From 013aa0e798d7511206012dfae04ae12fa35b5bfe Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Tue, 28 May 2024 17:50:46 +0200 Subject: [PATCH 01/20] add 'Array' type --- include/Configuration.h | 5 + include/Views/ViewStruct.h | 7 +- include/Views/ViewToolbar.h | 1 + resources/Spelunky2.json | 1348 ++---------------------- src/Configuration.cpp | 77 +- src/QtHelpers/TreeViewMemoryFields.cpp | 80 +- src/Views/ViewStruct.cpp | 20 + src/Views/ViewToolbar.cpp | 9 + 8 files changed, 285 insertions(+), 1262 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index cc95dd2..7fdcac5 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -126,6 +126,7 @@ namespace S2Plugin Online, IPv4Address, Double, + Array, }; struct VirtualFunction @@ -153,6 +154,8 @@ namespace S2Plugin std::string comment; // size in bytes size_t get_size() const; + // length, size of array etc. + size_t numberOfElements{0}; // For checking duplicate names bool operator==(const MemoryField& other) const @@ -209,6 +212,8 @@ namespace S2Plugin static std::string_view getTypeDisplayName(MemoryFieldType type); static size_t getBuiltInTypeSize(MemoryFieldType type); static bool isPointerType(MemoryFieldType type); + // unknown name will be created as DefaultStructType + MemoryField nameToMemoryField(const std::string& name) const; uintptr_t offsetForField(const std::vector& fields, std::string_view fieldUID, uintptr_t base_addr = 0) const; uintptr_t offsetForField(MemoryFieldType type, std::string_view fieldUID, uintptr_t base_addr = 0) const; diff --git a/include/Views/ViewStruct.h b/include/Views/ViewStruct.h index b864287..1b32277 100644 --- a/include/Views/ViewStruct.h +++ b/include/Views/ViewStruct.h @@ -20,7 +20,12 @@ namespace S2Plugin QSize sizeHint() const override; QSize minimumSizeHint() const override; - private: TreeViewMemoryFields* mMainTreeView; }; + + class ViewArray : public ViewStruct + { + public: + ViewArray(uintptr_t address, MemoryField field, size_t num, const std::string name, QWidget* parent = nullptr); + }; } // namespace S2Plugin diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index 19ff85a..d3e5fbc 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -26,6 +26,7 @@ namespace S2Plugin void showVirtualFunctions(uintptr_t address, const std::string& typeName); void showJournalPage(uintptr_t address); void showLevelGen(uintptr_t address); + void showArray(uintptr_t address, std::string name, std::string arrayTypeName, size_t length); public slots: ViewEntityDB* showEntityDB(); diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 9c4d020..c851312 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -6,8 +6,6 @@ "pointer_types": [ "StateItemsPointer", "LayerPointer", - "EntityListPointer", - "EntityUIDsListPointer", "SoundMeta", "IlluminationPointer", "ThemeInfoPointer", @@ -67,7 +65,6 @@ "StdVectorPointerParticleEmitterInfo", "SpecialLevelGeneration", "LevelGenDataPointer", - "AITargetsPointer", "SaveGameDataPointer", "ScreenLogoPointer", "ScreenIntroPointer", @@ -719,12 +716,7 @@ "24": "Can be bubbled", "25": "Can be telefragged", "26": "Disable updates?", - "27": "Kill entity when overlay lost?", - "28": "Unused", - "29": "Unused", - "30": "Unused", - "31": "Unused", - "32": "Unused" + "27": "Kill entity when overlay lost?" } }, { "field": "friction", "type": "Float" }, @@ -772,10 +764,7 @@ { "field": "max_speed", "type": "Float" }, { "field": "sprint_factor", "type": "Float" }, { "field": "jump", "type": "Float" }, - { "field": "glow_red", "type": "Float" }, - { "field": "glow_green", "type": "Float" }, - { "field": "glow_blue", "type": "Float" }, - { "field": "glow_alpha", "type": "Float" }, + { "field": "default_color", "type": "Color" }, { "field": "texture", "type": "TextureDBID" }, { "field": "technique", "type": "Dword" }, { "field": "tile_x", "type": "Dword" }, @@ -1166,48 +1155,6 @@ { "field": "unknown38", "type": "UnsignedDword" }, { "field": "unknown39", "type": "UnsignedDword" } ], - "BGMUnknown": [ - { "field": "unknown1", "type": "Float" }, - { "field": "unknown2", "type": "Float" }, - { "field": "unknown3", "type": "Float" }, - { "field": "unknown4", "type": "Float" }, - { "field": "unknown5", "type": "Float" }, - { "field": "unknown6", "type": "Float" }, - { "field": "unknown7", "type": "Float" }, - { "field": "unknown8", "type": "Float" }, - { "field": "unknown9", "type": "Float" }, - { "field": "unknown10", "type": "Float" }, - { "field": "unknown11", "type": "Float" }, - { "field": "unknown12", "type": "Float" }, - { "field": "unknown13", "type": "Float" }, - { "field": "unknown14", "type": "Float" }, - { "field": "unknown15", "type": "Float" }, - { "field": "unknown16", "type": "Float" }, - { "field": "unknown17", "type": "Float" }, - { "field": "unknown18", "type": "Float" }, - { "field": "unknown19", "type": "Float" }, - { "field": "unknown20", "type": "Float" }, - { "field": "unknown21", "type": "Float" }, - { "field": "unknown22", "type": "Float" }, - { "field": "unknown23", "type": "Float" }, - { "field": "unknown24", "type": "Float" }, - { "field": "unknown25", "type": "Float" }, - { "field": "unknown26", "type": "Float" }, - { "field": "unknown27", "type": "Float" }, - { "field": "unknown28", "type": "Float" }, - { "field": "unknown29", "type": "Float" }, - { "field": "unknown30", "type": "Float" }, - { "field": "unknown31", "type": "Float" }, - { "field": "unknown32", "type": "Float" }, - { "field": "unknown33", "type": "Float" }, - { "field": "unknown34", "type": "Float" }, - { "field": "unknown35", "type": "Float" }, - { "field": "unknown36", "type": "Float" }, - { "field": "unknown37", "type": "Float" }, - { "field": "unknown38", "type": "Float" }, - { "field": "unknown39", "type": "Float" }, - { "field": "unknown40", "type": "Float" } - ], "BackgroundMusicManagerPointer": [ { "field": "game_startup", "type": "SoundMeta" }, { "field": "main_backgroundtrack", "type": "SoundMeta" }, @@ -1229,21 +1176,21 @@ { "field": "unknown18", "type": "UnsignedByte" }, { "field": "unknown19", "type": "UnsignedByte" }, { "field": "unknown20", "type": "UnsignedByte" }, - { "field": "unknown21", "type": "BGMUnknown" }, - { "field": "unknown22", "type": "BGMUnknown" }, - { "field": "unknown23", "type": "BGMUnknown" }, - { "field": "unknown24", "type": "BGMUnknown" }, - { "field": "unknown25", "type": "BGMUnknown" }, - { "field": "unknown26", "type": "BGMUnknown" }, - { "field": "unknown27", "type": "BGMUnknown" }, - { "field": "unknown28", "type": "BGMUnknown" }, - { "field": "unknown29", "type": "BGMUnknown" }, - { "field": "unknown30", "type": "BGMUnknown" }, - { "field": "unknown31", "type": "BGMUnknown" }, - { "field": "unknown32", "type": "BGMUnknown" }, - { "field": "unknown33", "type": "BGMUnknown" }, - { "field": "unknown34", "type": "BGMUnknown" }, - { "field": "unknown35", "type": "BGMUnknown" }, + { "field": "unknown21", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown22", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown23", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown24", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown25", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown26", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown27", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown28", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown29", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown30", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown31", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown32", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown33", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown34", "type": "Array", "length":40, "arraytype": "Float" }, + { "field": "unknown35", "type": "Array", "length":40, "arraytype": "Float" }, { "field": "idle_counter", "type": "Float" }, { "field": "unknown22a", "type": "UnsignedDword" } ], @@ -1384,18 +1331,9 @@ { "field": "game_has_focus", "type": "Bool" }, { "field": "unknown9", "type": "Bool" }, { "field": "unknown10", "type": "Bool" }, - { "field": "keyboard1", "type": "InputsByControllerPointer" }, - { "field": "keyboard2", "type": "InputsByControllerPointer" }, - { "field": "keyboard3", "type": "InputsByControllerPointer" }, - { "field": "keyboard4", "type": "InputsByControllerPointer" }, - { "field": "x_input1", "type": "InputsByControllerPointer" }, - { "field": "x_input2", "type": "InputsByControllerPointer" }, - { "field": "x_input3", "type": "InputsByControllerPointer" }, - { "field": "x_input4", "type": "InputsByControllerPointer" }, - { "field": "other_controller1", "type": "InputsByControllerPointer" }, - { "field": "other_controller2", "type": "InputsByControllerPointer" }, - { "field": "other_controller3", "type": "InputsByControllerPointer" }, - { "field": "other_controller4", "type": "InputsByControllerPointer" }, + { "field": "keyboard", "type": "Array", "length": 4, "arraytype":"InputsByControllerPointer" }, + { "field": "x_input", "type": "Array", "length": 4, "arraytype":"InputsByControllerPointer" }, + { "field": "other_controller", "type": "Array", "length": 4, "arraytype":"InputsByControllerPointer" }, { "field": "input_index_p1", "type": "Byte" }, { "field": "input_index_p2", "type": "Byte" }, { "field": "input_index_p3", "type": "Byte" }, @@ -1859,26 +1797,7 @@ { "field": "time_text_info", "type": "TextRenderingInfoPointer" }, { "field": "time_value_text_info", "type": "TextRenderingInfoPointer" }, { "field": "sticker_count", "type": "UnsignedDword" }, - { "field": "sticker1", "type": "TextureRenderingInfo" }, - { "field": "sticker2", "type": "TextureRenderingInfo" }, - { "field": "sticker3", "type": "TextureRenderingInfo" }, - { "field": "sticker4", "type": "TextureRenderingInfo" }, - { "field": "sticker5", "type": "TextureRenderingInfo" }, - { "field": "sticker6", "type": "TextureRenderingInfo" }, - { "field": "sticker7", "type": "TextureRenderingInfo" }, - { "field": "sticker8", "type": "TextureRenderingInfo" }, - { "field": "sticker9", "type": "TextureRenderingInfo" }, - { "field": "sticker10", "type": "TextureRenderingInfo" }, - { "field": "sticker11", "type": "TextureRenderingInfo" }, - { "field": "sticker12", "type": "TextureRenderingInfo" }, - { "field": "sticker13", "type": "TextureRenderingInfo" }, - { "field": "sticker14", "type": "TextureRenderingInfo" }, - { "field": "sticker15", "type": "TextureRenderingInfo" }, - { "field": "sticker16", "type": "TextureRenderingInfo" }, - { "field": "sticker17", "type": "TextureRenderingInfo" }, - { "field": "sticker18", "type": "TextureRenderingInfo" }, - { "field": "sticker19", "type": "TextureRenderingInfo" }, - { "field": "sticker20", "type": "TextureRenderingInfo" } + { "field": "stickers", "type": "Array", "length":20, "arraytype": "TextureRenderingInfo" } ], "SaveGameDataPointer": [ { "field": "heap_offset", "type": "UnsignedQword" }, @@ -2013,6 +1932,10 @@ "comment": "std::pair&(std::pair&, Screen* src, uint8_t)" } ], + "SpritePosition": [ + { "field": "column", "type": "UnsignedDword" }, + { "field": "row", "type": "UnsignedDword" } + ], "ScreenMenuPointer": [ { "field": "base_screen", "type": "ScreenBase" }, { "field": "backlayer_transition_speed", "type": "Float" }, @@ -2096,72 +2019,15 @@ { "field": "menu_id", "type": "UnsignedDword" }, { "field": "transfer_to_menu_id", "type": "UnsignedDword" }, { "field": "menu_text_opacity", "type": "Float" }, - { "field": "spear_1_position", "type": "Float" }, - { "field": "spear_2_position", "type": "Float" }, - { "field": "spear_3_position", "type": "Float" }, - { "field": "spear_4_position", "type": "Float" }, - { "field": "spear_5_position", "type": "Float" }, - { "field": "spear_6_position", "type": "Float" }, - { - "field": "spear_1_dangler_animation_frame_col", - "type": "UnsignedDword" - }, + { "field": "spear_position", "type": "Array", "length": 6, "arraytype": "Float" }, { - "field": "spear_1_dangler_animation_frame_row", - "type": "UnsignedDword" + "field": "spear_dangler_sprite", + "type": "Array", + "length": 6, + "arraytype": "SpritePosition" }, - { - "field": "spear_2_dangler_animation_frame_col", - "type": "UnsignedDword" - }, - { - "field": "spear_2_dangler_animation_frame_row", - "type": "UnsignedDword" - }, - { - "field": "spear_3_dangler_animation_frame_col", - "type": "UnsignedDword" - }, - { - "field": "spear_3_dangler_animation_frame_row", - "type": "UnsignedDword" - }, - { - "field": "spear_4_dangler_animation_frame_col", - "type": "UnsignedDword" - }, - { - "field": "spear_4_dangler_animation_frame_row", - "type": "UnsignedDword" - }, - { - "field": "spear_5_dangler_animation_frame_col", - "type": "UnsignedDword" - }, - { - "field": "spear_5_dangler_animation_frame_row", - "type": "UnsignedDword" - }, - { - "field": "spear_6_dangler_animation_frame_col", - "type": "UnsignedDword" - }, - { - "field": "spear_6_dangler_animation_frame_row", - "type": "UnsignedDword" - }, - { "field": "spear_1_dangle_momentum", "type": "Float" }, - { "field": "spear_2_dangle_momentum", "type": "Float" }, - { "field": "spear_3_dangle_momentum", "type": "Float" }, - { "field": "spear_4_dangle_momentum", "type": "Float" }, - { "field": "spear_5_dangle_momentum", "type": "Float" }, - { "field": "spear_6_dangle_momentum", "type": "Float" }, - { "field": "spear_1_dangle_angle", "type": "Float" }, - { "field": "spear_2_dangle_angle", "type": "Float" }, - { "field": "spear_3_dangle_angle", "type": "Float" }, - { "field": "spear_4_dangle_angle", "type": "Float" }, - { "field": "spear_5_dangle_angle", "type": "Float" }, - { "field": "spear_6_dangle_angle", "type": "Float" }, + { "field": "spear_dangle_momentum", "type": "Array", "length": 6, "arraytype": "Float" }, + { "field": "spear_dangle_angle", "type": "Array", "length": 6, "arraytype": "Float" }, { "field": "play_scroll_descend", "type": "Float" }, { "field": "scroll_text", "type": "StringsTableID" }, { "field": "shake_offset_x", "type": "Float" }, @@ -2597,6 +2463,10 @@ { "field": "start_sidepanel", "type": "TextureRenderingInfo" }, { "field": "start_sidepanel_slidein_timer", "type": "Float" } ], + "LeftRight": [ + { "field": "left", "type": "Float" }, + { "field": "right", "type": "Float" } + ], "ScreenCharacterSelectPointer": [ { "field": "base_screen", "type": "ScreenBase" }, { "field": "unknown4", "type": "Float" }, @@ -2644,18 +2514,9 @@ }, { "field": "quick_select_panel_related", "type": "TextureRenderingInfo" }, { "field": "unknown_texture_rendering", "type": "TextureRenderingInfo" }, - { "field": "player_1_shutter_timer", "type": "Float" }, - { "field": "player_2_shutter_timer", "type": "Float" }, - { "field": "player_3_shutter_timer", "type": "Float" }, - { "field": "player_4_shutter_timer", "type": "Float" }, - { "field": "player_1_x_position", "type": "Float" }, - { "field": "player_2_x_position", "type": "Float" }, - { "field": "player_3_x_position", "type": "Float" }, - { "field": "player_4_x_position", "type": "Float" }, - { "field": "player_1_y_position", "type": "Float" }, - { "field": "player_2_y_position", "type": "Float" }, - { "field": "player_3_y_position", "type": "Float" }, - { "field": "player_4_y_position", "type": "Float" }, + { "field": "player_shutter_timer", "type": "Array", "length": 4, "arraytype": "Float" }, + { "field": "player_x_position", "type": "Array", "length": 4, "arraytype": "Float" }, + { "field": "player_y_position", "type": "Array", "length": 4, "arraytype": "Float" }, { "field": "unknown52", "type": "Float" }, { "field": "unknown53", "type": "Float" }, { "field": "unknown54", "type": "Float" }, @@ -2665,38 +2526,12 @@ "type": "Float", "comment": "faster timer to make them walk fast when selecting a different character?" }, - { "field": "player_1_left_arrow_slidein_timer", "type": "Float" }, - { "field": "player_1_right_arrow_slidein_timer", "type": "Float" }, - { "field": "player_2_left_arrow_slidein_timer", "type": "Float" }, - { "field": "player_2_right_arrow_slidein_timer", "type": "Float" }, - { "field": "player_3_left_arrow_slidein_timer", "type": "Float" }, - { "field": "player_3_right_arrow_slidein_timer", "type": "Float" }, - { "field": "player_4_left_arrow_slidein_timer", "type": "Float" }, - { "field": "player_4_right_arrow_slidein_timer", "type": "Float" }, - { "field": "player_1_facing_left", "type": "Bool" }, - { "field": "player_2_facing_left", "type": "Bool" }, - { "field": "player_3_facing_left", "type": "Bool" }, - { "field": "player_4_facing_left", "type": "Bool" }, - { "field": "player_1_quickselect_shown", "type": "Bool" }, - { "field": "player_2_quickselect_shown", "type": "Bool" }, - { "field": "player_3_quickselect_shown", "type": "Bool" }, - { "field": "player_4_quickselect_shown", "type": "Bool" }, - { "field": "player_1_quickselect_fadein_timer", "type": "Float" }, - { "field": "player_2_quickselect_fadein_timer", "type": "Float" }, - { "field": "player_3_quickselect_fadein_timer", "type": "Float" }, - { "field": "player_4_quickselect_fadein_timer", "type": "Float" }, - { "field": "player_1_quickselect_column", "type": "UnsignedDword" }, - { "field": "player_1_quickselect_row", "type": "UnsignedDword" }, - { "field": "player_2_quickselect_column", "type": "UnsignedDword" }, - { "field": "player_2_quickselect_row", "type": "UnsignedDword" }, - { "field": "player_3_quickselect_column", "type": "UnsignedDword" }, - { "field": "player_3_quickselect_row", "type": "UnsignedDword" }, - { "field": "player_4_quickselect_column", "type": "UnsignedDword" }, - { "field": "player_4_quickselect_row", "type": "UnsignedDword" }, - { "field": "player_1_quickselect_wiggle_angle", "type": "Float" }, - { "field": "player_2_quickselect_wiggle_angle", "type": "Float" }, - { "field": "player_3_quickselect_wiggle_angle", "type": "Float" }, - { "field": "player_4_quickselect_wiggle_angle", "type": "Float" }, + { "field": "player_arrow_slidein_timer", "type": "Array", "length": 4, "arraytype": "LeftRight" }, + { "field": "player_facing_left", "type": "Array", "length": 4, "arraytype": "Bool" }, + { "field": "player_quickselect_shown", "type": "Array", "length": 4, "arraytype": "Bool" }, + { "field": "player_quickselect_fadein_timer", "type": "Array", "length": 4, "arraytype": "Float" }, + { "field": "player_quickselect_sprite", "type": "Array", "length": 4, "arraytype": "SpritePosition" }, + { "field": "player_quickselect_wiggle_angle", "type": "Array", "length": 4, "arraytype": "Float" }, { "field": "another_timer", "type": "Float" }, { "field": "topleft_woodpanel_esc_slidein", "type": "Float" }, { "field": "bottom_woodpanel_slideout", "type": "Float" }, @@ -2760,12 +2595,7 @@ { "field": "disable_buttons?", "type": "Bool" }, { "field": "disable_controls", "type": "Bool" }, { "field": "unknown72", "type": "UnsignedDword" }, - { "field": "flying_thing_1", "type": "FlyingThing" }, - { "field": "flying_thing_2", "type": "FlyingThing" }, - { "field": "flying_thing_3", "type": "FlyingThing" }, - { "field": "flying_thing_4", "type": "FlyingThing" }, - { "field": "flying_thing_5", "type": "FlyingThing" }, - { "field": "flying_thing_6", "type": "FlyingThing" }, + { "field": "flying_thing", "type": "Array", "length": 6, "arraytype": "FlyingThing" }, { "field": "flying_thing_countdown", "type": "UnsignedWord", @@ -2814,10 +2644,7 @@ "field": "particle_torchflame_flames4", "type": "ParticleEmitterInfoPointer" }, - { "field": "torch_sound_entrence1", "type": "SoundMeta" }, - { "field": "torch_sound_entrence2", "type": "SoundMeta" }, - { "field": "torch_sound_entrence3", "type": "SoundMeta" }, - { "field": "torch_sound_entrence4", "type": "SoundMeta" }, + { "field": "torch_sound_entrence", "type": "Array", "length":4, "arraytype": "SoundMeta" }, { "field": "buttons_device_id0", "type": "UnsignedWord" }, { "field": "buttons_previous_device_id0", "type": "UnsignedWord" }, { "field": "buttons_device_id1", "type": "UnsignedWord" }, @@ -3250,8 +3077,8 @@ { "field": "unknown2b", "type": "UnsignedByte" }, { "field": "unknown2c", "type": "UnsignedByte" }, { "field": "unknown2d", "type": "UnsignedByte" }, - { "field": "waddler_storage", "type": "WaddlerStorage" }, - { "field": "waddler_storage_meta", "type": "WaddlerStorageMeta" }, + { "field": "waddler_storage", "type": "Array", "length": 99, "arraytype": "EntityDBID" }, + { "field": "waddler_storage_meta", "type": "Array", "length": 99, "arraytype": "UnsignedWord" }, { "field": "journal_progress_sticker_slots", "type": "JournalProgressStickerSlots", @@ -3503,7 +3330,7 @@ { "field": "layer1", "type": "LayerPointer" }, { "field": "logic", "type": "LogicList" }, { "field": "quests", "type": "QuestsInfoPointer" }, - { "field": "ai_targets", "type": "AITargetsPointer" }, + { "field": "ai_targets", "type": "Array", "pointer": true, "length": 128, "arraytype": "AITarget" }, { "field": "liquid_physics", "type": "LiquidPhysicsPointer" }, { "field": "particle_emitters", @@ -3616,16 +3443,6 @@ "comment": "get's incremented with a `lock'" } ], - "AITargetsPointer": [ - { "field": "target1", "type": "AITarget" }, - { "field": "target2", "type": "AITarget" }, - { "field": "target3", "type": "AITarget" }, - { "field": "target4", "type": "AITarget" }, - { "field": "target5", "type": "AITarget" }, - { "field": "target6", "type": "AITarget" }, - { "field": "target7", "type": "AITarget" }, - { "field": "target8", "type": "AITarget" } - ], "AITarget": [ { "field": "ai", "type": "EntityUID" }, { "field": "target", "type": "EntityUID" } @@ -3828,46 +3645,7 @@ "JournalProgressStickerSlots": [ { "field": "count", "type": "UnsignedByte" }, { "field": "padding", "type": "Skip", "offset": 1 }, - { "field": "slot1", "type": "JournalProgressStickerSlot" }, - { "field": "slot2", "type": "JournalProgressStickerSlot" }, - { "field": "slot3", "type": "JournalProgressStickerSlot" }, - { "field": "slot4", "type": "JournalProgressStickerSlot" }, - { "field": "slot5", "type": "JournalProgressStickerSlot" }, - { "field": "slot6", "type": "JournalProgressStickerSlot" }, - { "field": "slot7", "type": "JournalProgressStickerSlot" }, - { "field": "slot8", "type": "JournalProgressStickerSlot" }, - { "field": "slot9", "type": "JournalProgressStickerSlot" }, - { "field": "slot10", "type": "JournalProgressStickerSlot" }, - { "field": "slot11", "type": "JournalProgressStickerSlot" }, - { "field": "slot12", "type": "JournalProgressStickerSlot" }, - { "field": "slot13", "type": "JournalProgressStickerSlot" }, - { "field": "slot14", "type": "JournalProgressStickerSlot" }, - { "field": "slot15", "type": "JournalProgressStickerSlot" }, - { "field": "slot16", "type": "JournalProgressStickerSlot" }, - { "field": "slot17", "type": "JournalProgressStickerSlot" }, - { "field": "slot18", "type": "JournalProgressStickerSlot" }, - { "field": "slot19", "type": "JournalProgressStickerSlot" }, - { "field": "slot20", "type": "JournalProgressStickerSlot" }, - { "field": "slot21", "type": "JournalProgressStickerSlot" }, - { "field": "slot22", "type": "JournalProgressStickerSlot" }, - { "field": "slot23", "type": "JournalProgressStickerSlot" }, - { "field": "slot24", "type": "JournalProgressStickerSlot" }, - { "field": "slot25", "type": "JournalProgressStickerSlot" }, - { "field": "slot26", "type": "JournalProgressStickerSlot" }, - { "field": "slot27", "type": "JournalProgressStickerSlot" }, - { "field": "slot28", "type": "JournalProgressStickerSlot" }, - { "field": "slot29", "type": "JournalProgressStickerSlot" }, - { "field": "slot30", "type": "JournalProgressStickerSlot" }, - { "field": "slot31", "type": "JournalProgressStickerSlot" }, - { "field": "slot32", "type": "JournalProgressStickerSlot" }, - { "field": "slot33", "type": "JournalProgressStickerSlot" }, - { "field": "slot34", "type": "JournalProgressStickerSlot" }, - { "field": "slot35", "type": "JournalProgressStickerSlot" }, - { "field": "slot36", "type": "JournalProgressStickerSlot" }, - { "field": "slot37", "type": "JournalProgressStickerSlot" }, - { "field": "slot38", "type": "JournalProgressStickerSlot" }, - { "field": "slot39", "type": "JournalProgressStickerSlot" }, - { "field": "slot40", "type": "JournalProgressStickerSlot" } + { "field": "slot", "type": "Array", "length": 40, "arraytype": "JournalProgressStickerSlot" } ], "JournalProgressStainSlot": [ { "field": "x", "type": "Float" }, @@ -3886,36 +3664,7 @@ "JournalProgressStainSlots": [ { "field": "count", "type": "UnsignedByte" }, { "field": "padding", "type": "Skip", "offset": 3 }, - { "field": "slot1", "type": "JournalProgressStainSlot" }, - { "field": "slot2", "type": "JournalProgressStainSlot" }, - { "field": "slot3", "type": "JournalProgressStainSlot" }, - { "field": "slot4", "type": "JournalProgressStainSlot" }, - { "field": "slot5", "type": "JournalProgressStainSlot" }, - { "field": "slot6", "type": "JournalProgressStainSlot" }, - { "field": "slot7", "type": "JournalProgressStainSlot" }, - { "field": "slot8", "type": "JournalProgressStainSlot" }, - { "field": "slot9", "type": "JournalProgressStainSlot" }, - { "field": "slot10", "type": "JournalProgressStainSlot" }, - { "field": "slot11", "type": "JournalProgressStainSlot" }, - { "field": "slot12", "type": "JournalProgressStainSlot" }, - { "field": "slot13", "type": "JournalProgressStainSlot" }, - { "field": "slot14", "type": "JournalProgressStainSlot" }, - { "field": "slot15", "type": "JournalProgressStainSlot" }, - { "field": "slot16", "type": "JournalProgressStainSlot" }, - { "field": "slot17", "type": "JournalProgressStainSlot" }, - { "field": "slot18", "type": "JournalProgressStainSlot" }, - { "field": "slot19", "type": "JournalProgressStainSlot" }, - { "field": "slot20", "type": "JournalProgressStainSlot" }, - { "field": "slot21", "type": "JournalProgressStainSlot" }, - { "field": "slot22", "type": "JournalProgressStainSlot" }, - { "field": "slot23", "type": "JournalProgressStainSlot" }, - { "field": "slot24", "type": "JournalProgressStainSlot" }, - { "field": "slot25", "type": "JournalProgressStainSlot" }, - { "field": "slot26", "type": "JournalProgressStainSlot" }, - { "field": "slot27", "type": "JournalProgressStainSlot" }, - { "field": "slot28", "type": "JournalProgressStainSlot" }, - { "field": "slot29", "type": "JournalProgressStainSlot" }, - { "field": "slot30", "type": "JournalProgressStainSlot" } + { "field": "slot", "type": "Array", "length": 30, "arraytype": "JournalProgressStainSlot" } ], "JournalProgressThemeSlots": [ { "field": "count", "type": "UnsignedByte" }, @@ -4241,208 +3990,6 @@ { "field": "kapala", "type": "Bool" }, { "field": "scepter", "type": "Bool" } ], - "WaddlerStorage": [ - { "field": "slot1", "type": "EntityDBID" }, - { "field": "slot2", "type": "EntityDBID" }, - { "field": "slot3", "type": "EntityDBID" }, - { "field": "slot4", "type": "EntityDBID" }, - { "field": "slot5", "type": "EntityDBID" }, - { "field": "slot6", "type": "EntityDBID" }, - { "field": "slot7", "type": "EntityDBID" }, - { "field": "slot8", "type": "EntityDBID" }, - { "field": "slot9", "type": "EntityDBID" }, - { "field": "slot10", "type": "EntityDBID" }, - { "field": "slot11", "type": "EntityDBID" }, - { "field": "slot12", "type": "EntityDBID" }, - { "field": "slot13", "type": "EntityDBID" }, - { "field": "slot14", "type": "EntityDBID" }, - { "field": "slot15", "type": "EntityDBID" }, - { "field": "slot16", "type": "EntityDBID" }, - { "field": "slot17", "type": "EntityDBID" }, - { "field": "slot18", "type": "EntityDBID" }, - { "field": "slot19", "type": "EntityDBID" }, - { "field": "slot20", "type": "EntityDBID" }, - { "field": "slot21", "type": "EntityDBID" }, - { "field": "slot22", "type": "EntityDBID" }, - { "field": "slot23", "type": "EntityDBID" }, - { "field": "slot24", "type": "EntityDBID" }, - { "field": "slot25", "type": "EntityDBID" }, - { "field": "slot26", "type": "EntityDBID" }, - { "field": "slot27", "type": "EntityDBID" }, - { "field": "slot28", "type": "EntityDBID" }, - { "field": "slot29", "type": "EntityDBID" }, - { "field": "slot30", "type": "EntityDBID" }, - { "field": "slot31", "type": "EntityDBID" }, - { "field": "slot32", "type": "EntityDBID" }, - { "field": "slot33", "type": "EntityDBID" }, - { "field": "slot34", "type": "EntityDBID" }, - { "field": "slot35", "type": "EntityDBID" }, - { "field": "slot36", "type": "EntityDBID" }, - { "field": "slot37", "type": "EntityDBID" }, - { "field": "slot38", "type": "EntityDBID" }, - { "field": "slot39", "type": "EntityDBID" }, - { "field": "slot40", "type": "EntityDBID" }, - { "field": "slot41", "type": "EntityDBID" }, - { "field": "slot42", "type": "EntityDBID" }, - { "field": "slot43", "type": "EntityDBID" }, - { "field": "slot44", "type": "EntityDBID" }, - { "field": "slot45", "type": "EntityDBID" }, - { "field": "slot46", "type": "EntityDBID" }, - { "field": "slot47", "type": "EntityDBID" }, - { "field": "slot48", "type": "EntityDBID" }, - { "field": "slot49", "type": "EntityDBID" }, - { "field": "slot50", "type": "EntityDBID" }, - { "field": "slot51", "type": "EntityDBID" }, - { "field": "slot52", "type": "EntityDBID" }, - { "field": "slot53", "type": "EntityDBID" }, - { "field": "slot54", "type": "EntityDBID" }, - { "field": "slot55", "type": "EntityDBID" }, - { "field": "slot56", "type": "EntityDBID" }, - { "field": "slot57", "type": "EntityDBID" }, - { "field": "slot58", "type": "EntityDBID" }, - { "field": "slot59", "type": "EntityDBID" }, - { "field": "slot60", "type": "EntityDBID" }, - { "field": "slot61", "type": "EntityDBID" }, - { "field": "slot62", "type": "EntityDBID" }, - { "field": "slot63", "type": "EntityDBID" }, - { "field": "slot64", "type": "EntityDBID" }, - { "field": "slot65", "type": "EntityDBID" }, - { "field": "slot66", "type": "EntityDBID" }, - { "field": "slot67", "type": "EntityDBID" }, - { "field": "slot68", "type": "EntityDBID" }, - { "field": "slot69", "type": "EntityDBID" }, - { "field": "slot70", "type": "EntityDBID" }, - { "field": "slot71", "type": "EntityDBID" }, - { "field": "slot72", "type": "EntityDBID" }, - { "field": "slot73", "type": "EntityDBID" }, - { "field": "slot74", "type": "EntityDBID" }, - { "field": "slot75", "type": "EntityDBID" }, - { "field": "slot76", "type": "EntityDBID" }, - { "field": "slot77", "type": "EntityDBID" }, - { "field": "slot78", "type": "EntityDBID" }, - { "field": "slot79", "type": "EntityDBID" }, - { "field": "slot80", "type": "EntityDBID" }, - { "field": "slot81", "type": "EntityDBID" }, - { "field": "slot82", "type": "EntityDBID" }, - { "field": "slot83", "type": "EntityDBID" }, - { "field": "slot84", "type": "EntityDBID" }, - { "field": "slot85", "type": "EntityDBID" }, - { "field": "slot86", "type": "EntityDBID" }, - { "field": "slot87", "type": "EntityDBID" }, - { "field": "slot88", "type": "EntityDBID" }, - { "field": "slot89", "type": "EntityDBID" }, - { "field": "slot90", "type": "EntityDBID" }, - { "field": "slot91", "type": "EntityDBID" }, - { "field": "slot92", "type": "EntityDBID" }, - { "field": "slot93", "type": "EntityDBID" }, - { "field": "slot94", "type": "EntityDBID" }, - { "field": "slot95", "type": "EntityDBID" }, - { "field": "slot96", "type": "EntityDBID" }, - { "field": "slot97", "type": "EntityDBID" }, - { "field": "slot98", "type": "EntityDBID" }, - { "field": "slot99", "type": "EntityDBID" } - ], - "WaddlerStorageMeta": [ - { "field": "slot1", "type": "Word" }, - { "field": "slot2", "type": "Word" }, - { "field": "slot3", "type": "Word" }, - { "field": "slot4", "type": "Word" }, - { "field": "slot5", "type": "Word" }, - { "field": "slot6", "type": "Word" }, - { "field": "slot7", "type": "Word" }, - { "field": "slot8", "type": "Word" }, - { "field": "slot9", "type": "Word" }, - { "field": "slot10", "type": "Word" }, - { "field": "slot11", "type": "Word" }, - { "field": "slot12", "type": "Word" }, - { "field": "slot13", "type": "Word" }, - { "field": "slot14", "type": "Word" }, - { "field": "slot15", "type": "Word" }, - { "field": "slot16", "type": "Word" }, - { "field": "slot17", "type": "Word" }, - { "field": "slot18", "type": "Word" }, - { "field": "slot19", "type": "Word" }, - { "field": "slot20", "type": "Word" }, - { "field": "slot21", "type": "Word" }, - { "field": "slot22", "type": "Word" }, - { "field": "slot23", "type": "Word" }, - { "field": "slot24", "type": "Word" }, - { "field": "slot25", "type": "Word" }, - { "field": "slot26", "type": "Word" }, - { "field": "slot27", "type": "Word" }, - { "field": "slot28", "type": "Word" }, - { "field": "slot29", "type": "Word" }, - { "field": "slot30", "type": "Word" }, - { "field": "slot31", "type": "Word" }, - { "field": "slot32", "type": "Word" }, - { "field": "slot33", "type": "Word" }, - { "field": "slot34", "type": "Word" }, - { "field": "slot35", "type": "Word" }, - { "field": "slot36", "type": "Word" }, - { "field": "slot37", "type": "Word" }, - { "field": "slot38", "type": "Word" }, - { "field": "slot39", "type": "Word" }, - { "field": "slot40", "type": "Word" }, - { "field": "slot41", "type": "Word" }, - { "field": "slot42", "type": "Word" }, - { "field": "slot43", "type": "Word" }, - { "field": "slot44", "type": "Word" }, - { "field": "slot45", "type": "Word" }, - { "field": "slot46", "type": "Word" }, - { "field": "slot47", "type": "Word" }, - { "field": "slot48", "type": "Word" }, - { "field": "slot49", "type": "Word" }, - { "field": "slot50", "type": "Word" }, - { "field": "slot51", "type": "Word" }, - { "field": "slot52", "type": "Word" }, - { "field": "slot53", "type": "Word" }, - { "field": "slot54", "type": "Word" }, - { "field": "slot55", "type": "Word" }, - { "field": "slot56", "type": "Word" }, - { "field": "slot57", "type": "Word" }, - { "field": "slot58", "type": "Word" }, - { "field": "slot59", "type": "Word" }, - { "field": "slot60", "type": "Word" }, - { "field": "slot61", "type": "Word" }, - { "field": "slot62", "type": "Word" }, - { "field": "slot63", "type": "Word" }, - { "field": "slot64", "type": "Word" }, - { "field": "slot65", "type": "Word" }, - { "field": "slot66", "type": "Word" }, - { "field": "slot67", "type": "Word" }, - { "field": "slot68", "type": "Word" }, - { "field": "slot69", "type": "Word" }, - { "field": "slot70", "type": "Word" }, - { "field": "slot71", "type": "Word" }, - { "field": "slot72", "type": "Word" }, - { "field": "slot73", "type": "Word" }, - { "field": "slot74", "type": "Word" }, - { "field": "slot75", "type": "Word" }, - { "field": "slot76", "type": "Word" }, - { "field": "slot77", "type": "Word" }, - { "field": "slot78", "type": "Word" }, - { "field": "slot79", "type": "Word" }, - { "field": "slot80", "type": "Word" }, - { "field": "slot81", "type": "Word" }, - { "field": "slot82", "type": "Word" }, - { "field": "slot83", "type": "Word" }, - { "field": "slot84", "type": "Word" }, - { "field": "slot85", "type": "Word" }, - { "field": "slot86", "type": "Word" }, - { "field": "slot87", "type": "Word" }, - { "field": "slot88", "type": "Word" }, - { "field": "slot89", "type": "Word" }, - { "field": "slot90", "type": "Word" }, - { "field": "slot91", "type": "Word" }, - { "field": "slot92", "type": "Word" }, - { "field": "slot93", "type": "Word" }, - { "field": "slot94", "type": "Word" }, - { "field": "slot95", "type": "Word" }, - { "field": "slot96", "type": "Word" }, - { "field": "slot97", "type": "Word" }, - { "field": "slot98", "type": "Word" }, - { "field": "slot99", "type": "Word" } - ], "CameraPointer": [ { "field": "bounds", "type": "CameraBounds" }, { @@ -5519,49 +5066,13 @@ { "field": "unknown50", "type": "ParticleEmitterInfoPointer" }, { "field": "unknown51", "type": "ParticleEmitterInfoPointer" }, { "field": "unknown52", "type": "ParticleEmitterInfoPointer" }, - { "field": "lava_bubble_pos1", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos2", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos3", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos4", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos5", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos6", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos7", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos8", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos9", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos10", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos11", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos12", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos13", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos14", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "lava_bubble_pos15", "type": "ScreenArenaScoreLavaBubble" }, - { "field": "unknown68", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown69", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown70", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown71", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown72", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown73", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown74", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown75", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown76", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown77", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown78", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown79", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown80", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown81", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown82", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown83", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown84", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown85", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown86", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown87", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown88", "type": "ScreenArenaScoreUnknown" }, - { "field": "unknown89", "type": "ScreenArenaScoreUnknown" }, - { "field": "-", "type": "Skip", "offset": 6728 }, + { "field": "lava_bubble_pos", "type": "Array", "length": 15, "arraytype": "ScreenArenaScoreLavaBubble" }, + { "field": "unknown68", "type": "Array", "length": 80, "arraytype": "ScreenArenaScoreUnknown" }, { "field": "padding?", "type": "UnsignedDword" }, { "field": "end?", "type": "UnsignedQword", - "comment": "160 structs above, who know what for, also this is just allocator thing, not actual value" + "comment": "this is just allocator thing, not actual value" } ], "ScreenControls": [ @@ -5629,14 +5140,7 @@ }, { "field": "theme_indicator", "type": "TextureRenderingInfo" }, { "field": "bricks_bottom_left", "type": "TextureRenderingInfo" }, - { "field": "grid_background_row_0", "type": "TextureRenderingInfo" }, - { "field": "grid_background_row_1", "type": "TextureRenderingInfo" }, - { "field": "grid_background_row_2", "type": "TextureRenderingInfo" }, - { "field": "grid_background_row_3", "type": "TextureRenderingInfo" }, - { "field": "grid_background_row_4", "type": "TextureRenderingInfo" }, - { "field": "grid_background_row_5", "type": "TextureRenderingInfo" }, - { "field": "grid_background_row_6", "type": "TextureRenderingInfo" }, - { "field": "grid_background_row_7", "type": "TextureRenderingInfo" }, + { "field": "grid_background_row", "type": "Array", "length": 8, "arraytype": "TextureRenderingInfo" }, { "field": "grid_background_disabled_cross", "type": "TextureRenderingInfo" @@ -6160,20 +5664,20 @@ { "field": "padding4", "type": "UnsignedByte" }, { "field": "padding5", "type": "UnsignedByte" }, { "field": "skip4", "type": "Skip", "offset": 1020 }, - { "field": "deathcount_dwelling", "type": "SaveGameDeathCountLevels" }, + { "field": "deathcount_dwelling", "type": "Array", "length": 255, "arraytype": "UnsignedDword" }, { "field": "deathcount_jungle_volcana", - "type": "SaveGameDeathCountLevels" + "type": "Array", "length": 255, "arraytype": "UnsignedDword" }, - { "field": "deathcount_olmec", "type": "SaveGameDeathCountLevels" }, + { "field": "deathcount_olmec", "type": "Array", "length": 255, "arraytype": "UnsignedDword" }, { "field": "deathcount_tidepool_temple", - "type": "SaveGameDeathCountLevels" + "type": "Array", "length": 255, "arraytype": "UnsignedDword" }, - { "field": "deathcount_icecaves", "type": "SaveGameDeathCountLevels" }, - { "field": "deathcount_neobabylon", "type": "SaveGameDeathCountLevels" }, - { "field": "deathcount_sunkencity", "type": "SaveGameDeathCountLevels" }, - { "field": "deathcount_cosmicocean", "type": "SaveGameDeathCountLevels" }, + { "field": "deathcount_icecaves", "type": "Array", "length": 255, "arraytype": "UnsignedDword" }, + { "field": "deathcount_neobabylon", "type": "Array", "length": 255, "arraytype": "UnsignedDword" }, + { "field": "deathcount_sunkencity", "type": "Array", "length": 255, "arraytype": "UnsignedDword" }, + { "field": "deathcount_cosmicocean", "type": "Array", "length": 255, "arraytype": "UnsignedDword" }, { "field": "time_total", "type": "Qword" }, { "field": "time_best", "type": "Dword" }, { "field": "character_deaths", "type": "SaveGameCharacterDeaths" }, @@ -6190,18 +5694,20 @@ { "field": "padding6", "type": "UnsignedByte" }, { "field": "score_last", "type": "UnsignedDword" }, { "field": "time_last", "type": "UnsignedDword" }, - { "field": "stickers", "type": "SaveGameStickers" }, + { "field": "stickers", "type": "Array", "length": 20, "arraytype": "EntityDBID" }, { "field": "skip8", "type": "Skip", "offset": 40, "comment": "first dword is a mask(?) that determines horizontal spacing between stickers" }, - { "field": "sticker_angles", "type": "SaveGameStickerAngles" }, + { "field": "sticker_angles", "type": "Array", "length": 20, "arraytype": "Float" }, { "field": "skip9", "type": "Skip", "offset": 40 }, { "field": "sticker_vert_offsets", - "type": "SaveGameStickerVerticalOffsets" + "type": "Array", + "length": 20, + "arraytype": "Float" }, { "field": "skip10", "type": "Skip", "offset": 40 }, { "field": "players", "type": "SaveGamePlayers" }, @@ -6209,68 +5715,29 @@ { "field": "constellation", "type": "Constellation" } ], "Constellation": [ - { "field": "star_count", "type": "UnsignedDword" }, - { "field": "stars", "type": "ConstellationStars" }, + { "field": "star_count", "type": "UnsignedByte" }, + { "field": "unknown1", "type": "UnsignedByte" }, + { "field": "unknown2", "type": "UnsignedByte" }, + { "field": "unknown3", "type": "UnsignedByte" }, + { "field": "stars", "type": "Array", "length": 45, "arraytype": "ConstellationStar" }, { "field": "scale", "type": "Float" }, { "field": "line_count", "type": "UnsignedByte" }, { "field": "lines", - "type": "ConstellationLines", + "type": "Array", + "length": 90, + "arraytype": "ConstellationLine", "comment": "You'd only need 44 lines if you have 45 stars, but there is room for 91. Could be to draw two lines on top of each other to make it brighter." }, + { "field": "padding1", "type": "UnsignedByte" }, + { "field": "padding2", "type": "UnsignedByte" }, + { "field": "padding3", "type": "UnsignedByte" }, { "field": "line_red_intensity", "type": "Float", "comment": "0 = normal white, npc_kills 8 - 16 = 0.5 - 1.0 (pink to deep red for criminalis)" } ], - "ConstellationStars": [ - { "field": "star_1", "type": "ConstellationStar" }, - { "field": "star_2", "type": "ConstellationStar" }, - { "field": "star_3", "type": "ConstellationStar" }, - { "field": "star_4", "type": "ConstellationStar" }, - { "field": "star_5", "type": "ConstellationStar" }, - { "field": "star_6", "type": "ConstellationStar" }, - { "field": "star_7", "type": "ConstellationStar" }, - { "field": "star_8", "type": "ConstellationStar" }, - { "field": "star_9", "type": "ConstellationStar" }, - { "field": "star_10", "type": "ConstellationStar" }, - { "field": "star_11", "type": "ConstellationStar" }, - { "field": "star_12", "type": "ConstellationStar" }, - { "field": "star_13", "type": "ConstellationStar" }, - { "field": "star_14", "type": "ConstellationStar" }, - { "field": "star_15", "type": "ConstellationStar" }, - { "field": "star_16", "type": "ConstellationStar" }, - { "field": "star_17", "type": "ConstellationStar" }, - { "field": "star_18", "type": "ConstellationStar" }, - { "field": "star_19", "type": "ConstellationStar" }, - { "field": "star_20", "type": "ConstellationStar" }, - { "field": "star_21", "type": "ConstellationStar" }, - { "field": "star_22", "type": "ConstellationStar" }, - { "field": "star_23", "type": "ConstellationStar" }, - { "field": "star_24", "type": "ConstellationStar" }, - { "field": "star_25", "type": "ConstellationStar" }, - { "field": "star_26", "type": "ConstellationStar" }, - { "field": "star_27", "type": "ConstellationStar" }, - { "field": "star_28", "type": "ConstellationStar" }, - { "field": "star_29", "type": "ConstellationStar" }, - { "field": "star_30", "type": "ConstellationStar" }, - { "field": "star_31", "type": "ConstellationStar" }, - { "field": "star_32", "type": "ConstellationStar" }, - { "field": "star_33", "type": "ConstellationStar" }, - { "field": "star_34", "type": "ConstellationStar" }, - { "field": "star_35", "type": "ConstellationStar" }, - { "field": "star_36", "type": "ConstellationStar" }, - { "field": "star_37", "type": "ConstellationStar" }, - { "field": "star_38", "type": "ConstellationStar" }, - { "field": "star_39", "type": "ConstellationStar" }, - { "field": "star_40", "type": "ConstellationStar" }, - { "field": "star_41", "type": "ConstellationStar" }, - { "field": "star_42", "type": "ConstellationStar" }, - { "field": "star_43", "type": "ConstellationStar" }, - { "field": "star_44", "type": "ConstellationStar" }, - { "field": "star_45", "type": "ConstellationStar" } - ], "ConstellationStar": [ { "field": "type", @@ -6302,101 +5769,6 @@ "comment": "might have something to do with how they are laid out on the path, having/being offshoots etc" } ], - "ConstellationLines": [ - { "field": "line_1", "type": "ConstellationLine" }, - { "field": "line_2", "type": "ConstellationLine" }, - { "field": "line_3", "type": "ConstellationLine" }, - { "field": "line_4", "type": "ConstellationLine" }, - { "field": "line_5", "type": "ConstellationLine" }, - { "field": "line_6", "type": "ConstellationLine" }, - { "field": "line_7", "type": "ConstellationLine" }, - { "field": "line_8", "type": "ConstellationLine" }, - { "field": "line_9", "type": "ConstellationLine" }, - { "field": "line_10", "type": "ConstellationLine" }, - { "field": "line_11", "type": "ConstellationLine" }, - { "field": "line_12", "type": "ConstellationLine" }, - { "field": "line_13", "type": "ConstellationLine" }, - { "field": "line_14", "type": "ConstellationLine" }, - { "field": "line_15", "type": "ConstellationLine" }, - { "field": "line_16", "type": "ConstellationLine" }, - { "field": "line_17", "type": "ConstellationLine" }, - { "field": "line_18", "type": "ConstellationLine" }, - { "field": "line_19", "type": "ConstellationLine" }, - { "field": "line_20", "type": "ConstellationLine" }, - { "field": "line_21", "type": "ConstellationLine" }, - { "field": "line_22", "type": "ConstellationLine" }, - { "field": "line_23", "type": "ConstellationLine" }, - { "field": "line_24", "type": "ConstellationLine" }, - { "field": "line_25", "type": "ConstellationLine" }, - { "field": "line_26", "type": "ConstellationLine" }, - { "field": "line_27", "type": "ConstellationLine" }, - { "field": "line_28", "type": "ConstellationLine" }, - { "field": "line_29", "type": "ConstellationLine" }, - { "field": "line_30", "type": "ConstellationLine" }, - { "field": "line_31", "type": "ConstellationLine" }, - { "field": "line_32", "type": "ConstellationLine" }, - { "field": "line_33", "type": "ConstellationLine" }, - { "field": "line_34", "type": "ConstellationLine" }, - { "field": "line_35", "type": "ConstellationLine" }, - { "field": "line_36", "type": "ConstellationLine" }, - { "field": "line_37", "type": "ConstellationLine" }, - { "field": "line_38", "type": "ConstellationLine" }, - { "field": "line_39", "type": "ConstellationLine" }, - { "field": "line_40", "type": "ConstellationLine" }, - { "field": "line_41", "type": "ConstellationLine" }, - { "field": "line_42", "type": "ConstellationLine" }, - { "field": "line_43", "type": "ConstellationLine" }, - { "field": "line_44", "type": "ConstellationLine" }, - { "field": "line_45", "type": "ConstellationLine" }, - { "field": "line_46", "type": "ConstellationLine" }, - { "field": "line_47", "type": "ConstellationLine" }, - { "field": "line_48", "type": "ConstellationLine" }, - { "field": "line_49", "type": "ConstellationLine" }, - { "field": "line_50", "type": "ConstellationLine" }, - { "field": "line_51", "type": "ConstellationLine" }, - { "field": "line_52", "type": "ConstellationLine" }, - { "field": "line_53", "type": "ConstellationLine" }, - { "field": "line_54", "type": "ConstellationLine" }, - { "field": "line_55", "type": "ConstellationLine" }, - { "field": "line_56", "type": "ConstellationLine" }, - { "field": "line_57", "type": "ConstellationLine" }, - { "field": "line_58", "type": "ConstellationLine" }, - { "field": "line_59", "type": "ConstellationLine" }, - { "field": "line_60", "type": "ConstellationLine" }, - { "field": "line_61", "type": "ConstellationLine" }, - { "field": "line_62", "type": "ConstellationLine" }, - { "field": "line_63", "type": "ConstellationLine" }, - { "field": "line_64", "type": "ConstellationLine" }, - { "field": "line_65", "type": "ConstellationLine" }, - { "field": "line_66", "type": "ConstellationLine" }, - { "field": "line_67", "type": "ConstellationLine" }, - { "field": "line_68", "type": "ConstellationLine" }, - { "field": "line_69", "type": "ConstellationLine" }, - { "field": "line_70", "type": "ConstellationLine" }, - { "field": "line_71", "type": "ConstellationLine" }, - { "field": "line_72", "type": "ConstellationLine" }, - { "field": "line_73", "type": "ConstellationLine" }, - { "field": "line_74", "type": "ConstellationLine" }, - { "field": "line_75", "type": "ConstellationLine" }, - { "field": "line_76", "type": "ConstellationLine" }, - { "field": "line_77", "type": "ConstellationLine" }, - { "field": "line_78", "type": "ConstellationLine" }, - { "field": "line_79", "type": "ConstellationLine" }, - { "field": "line_80", "type": "ConstellationLine" }, - { "field": "line_81", "type": "ConstellationLine" }, - { "field": "line_82", "type": "ConstellationLine" }, - { "field": "line_83", "type": "ConstellationLine" }, - { "field": "line_84", "type": "ConstellationLine" }, - { "field": "line_85", "type": "ConstellationLine" }, - { "field": "line_86", "type": "ConstellationLine" }, - { "field": "line_87", "type": "ConstellationLine" }, - { "field": "line_88", "type": "ConstellationLine" }, - { "field": "line_89", "type": "ConstellationLine" }, - { "field": "line_90", "type": "ConstellationLine" }, - { "field": "padding1", "type": "UnsignedByte" }, - { "field": "padding2", "type": "UnsignedByte" }, - { "field": "padding3", "type": "UnsignedByte" } - ], "ConstellationLine": [ { "field": "from", "type": "UnsignedByte", "comment": "star index" }, { "field": "to", "type": "UnsignedByte" } @@ -6768,335 +6140,12 @@ { "field": "Cat", "type": "UnsignedByte" }, { "field": "Hamster", "type": "UnsignedByte" } ], - "SaveGameStickers": [ - { "field": "sticker1", "type": "EntityDBID" }, - { "field": "sticker2", "type": "EntityDBID" }, - { "field": "sticker3", "type": "EntityDBID" }, - { "field": "sticker4", "type": "EntityDBID" }, - { "field": "sticker5", "type": "EntityDBID" }, - { "field": "sticker6", "type": "EntityDBID" }, - { "field": "sticker7", "type": "EntityDBID" }, - { "field": "sticker8", "type": "EntityDBID" }, - { "field": "sticker9", "type": "EntityDBID" }, - { "field": "sticker10", "type": "EntityDBID" }, - { "field": "sticker11", "type": "EntityDBID" }, - { "field": "sticker12", "type": "EntityDBID" }, - { "field": "sticker13", "type": "EntityDBID" }, - { "field": "sticker14", "type": "EntityDBID" }, - { "field": "sticker15", "type": "EntityDBID" }, - { "field": "sticker16", "type": "EntityDBID" }, - { "field": "sticker17", "type": "EntityDBID" }, - { "field": "sticker18", "type": "EntityDBID" }, - { "field": "sticker19", "type": "EntityDBID" }, - { "field": "sticker20", "type": "EntityDBID" } - ], - "SaveGameStickerAngles": [ - { "field": "sticker1", "type": "Float" }, - { "field": "sticker2", "type": "Float" }, - { "field": "sticker3", "type": "Float" }, - { "field": "sticker4", "type": "Float" }, - { "field": "sticker5", "type": "Float" }, - { "field": "sticker6", "type": "Float" }, - { "field": "sticker7", "type": "Float" }, - { "field": "sticker8", "type": "Float" }, - { "field": "sticker9", "type": "Float" }, - { "field": "sticker10", "type": "Float" }, - { "field": "sticker11", "type": "Float" }, - { "field": "sticker12", "type": "Float" }, - { "field": "sticker13", "type": "Float" }, - { "field": "sticker14", "type": "Float" }, - { "field": "sticker15", "type": "Float" }, - { "field": "sticker16", "type": "Float" }, - { "field": "sticker17", "type": "Float" }, - { "field": "sticker18", "type": "Float" }, - { "field": "sticker19", "type": "Float" }, - { "field": "sticker20", "type": "Float" } - ], - "SaveGameStickerVerticalOffsets": [ - { "field": "sticker1", "type": "Float" }, - { "field": "sticker2", "type": "Float" }, - { "field": "sticker3", "type": "Float" }, - { "field": "sticker4", "type": "Float" }, - { "field": "sticker5", "type": "Float" }, - { "field": "sticker6", "type": "Float" }, - { "field": "sticker7", "type": "Float" }, - { "field": "sticker8", "type": "Float" }, - { "field": "sticker9", "type": "Float" }, - { "field": "sticker10", "type": "Float" }, - { "field": "sticker11", "type": "Float" }, - { "field": "sticker12", "type": "Float" }, - { "field": "sticker13", "type": "Float" }, - { "field": "sticker14", "type": "Float" }, - { "field": "sticker15", "type": "Float" }, - { "field": "sticker16", "type": "Float" }, - { "field": "sticker17", "type": "Float" }, - { "field": "sticker18", "type": "Float" }, - { "field": "sticker19", "type": "Float" }, - { "field": "sticker20", "type": "Float" } - ], "SaveGamePlayers": [ { "field": "player1", "type": "State8", "ref": "character_states" }, { "field": "player2", "type": "State8", "ref": "character_states" }, { "field": "player3", "type": "State8", "ref": "character_states" }, { "field": "player4", "type": "State8", "ref": "character_states" } ], - "SaveGameDeathCountLevels": [ - { "field": "level0", "type": "UnsignedDword" }, - { "field": "level1", "type": "UnsignedDword" }, - { "field": "level2", "type": "UnsignedDword" }, - { "field": "level3", "type": "UnsignedDword" }, - { "field": "level4", "type": "UnsignedDword" }, - { "field": "level5", "type": "UnsignedDword" }, - { "field": "level6", "type": "UnsignedDword" }, - { "field": "level7", "type": "UnsignedDword" }, - { "field": "level8", "type": "UnsignedDword" }, - { "field": "level9", "type": "UnsignedDword" }, - { "field": "level10", "type": "UnsignedDword" }, - { "field": "level11", "type": "UnsignedDword" }, - { "field": "level12", "type": "UnsignedDword" }, - { "field": "level13", "type": "UnsignedDword" }, - { "field": "level14", "type": "UnsignedDword" }, - { "field": "level15", "type": "UnsignedDword" }, - { "field": "level16", "type": "UnsignedDword" }, - { "field": "level17", "type": "UnsignedDword" }, - { "field": "level18", "type": "UnsignedDword" }, - { "field": "level19", "type": "UnsignedDword" }, - { "field": "level20", "type": "UnsignedDword" }, - { "field": "level21", "type": "UnsignedDword" }, - { "field": "level22", "type": "UnsignedDword" }, - { "field": "level23", "type": "UnsignedDword" }, - { "field": "level24", "type": "UnsignedDword" }, - { "field": "level25", "type": "UnsignedDword" }, - { "field": "level26", "type": "UnsignedDword" }, - { "field": "level27", "type": "UnsignedDword" }, - { "field": "level28", "type": "UnsignedDword" }, - { "field": "level29", "type": "UnsignedDword" }, - { "field": "level30", "type": "UnsignedDword" }, - { "field": "level31", "type": "UnsignedDword" }, - { "field": "level32", "type": "UnsignedDword" }, - { "field": "level33", "type": "UnsignedDword" }, - { "field": "level34", "type": "UnsignedDword" }, - { "field": "level35", "type": "UnsignedDword" }, - { "field": "level36", "type": "UnsignedDword" }, - { "field": "level37", "type": "UnsignedDword" }, - { "field": "level38", "type": "UnsignedDword" }, - { "field": "level39", "type": "UnsignedDword" }, - { "field": "level40", "type": "UnsignedDword" }, - { "field": "level41", "type": "UnsignedDword" }, - { "field": "level42", "type": "UnsignedDword" }, - { "field": "level43", "type": "UnsignedDword" }, - { "field": "level44", "type": "UnsignedDword" }, - { "field": "level45", "type": "UnsignedDword" }, - { "field": "level46", "type": "UnsignedDword" }, - { "field": "level47", "type": "UnsignedDword" }, - { "field": "level48", "type": "UnsignedDword" }, - { "field": "level49", "type": "UnsignedDword" }, - { "field": "level50", "type": "UnsignedDword" }, - { "field": "level51", "type": "UnsignedDword" }, - { "field": "level52", "type": "UnsignedDword" }, - { "field": "level53", "type": "UnsignedDword" }, - { "field": "level54", "type": "UnsignedDword" }, - { "field": "level55", "type": "UnsignedDword" }, - { "field": "level56", "type": "UnsignedDword" }, - { "field": "level57", "type": "UnsignedDword" }, - { "field": "level58", "type": "UnsignedDword" }, - { "field": "level59", "type": "UnsignedDword" }, - { "field": "level60", "type": "UnsignedDword" }, - { "field": "level61", "type": "UnsignedDword" }, - { "field": "level62", "type": "UnsignedDword" }, - { "field": "level63", "type": "UnsignedDword" }, - { "field": "level64", "type": "UnsignedDword" }, - { "field": "level65", "type": "UnsignedDword" }, - { "field": "level66", "type": "UnsignedDword" }, - { "field": "level67", "type": "UnsignedDword" }, - { "field": "level68", "type": "UnsignedDword" }, - { "field": "level69", "type": "UnsignedDword" }, - { "field": "level70", "type": "UnsignedDword" }, - { "field": "level71", "type": "UnsignedDword" }, - { "field": "level72", "type": "UnsignedDword" }, - { "field": "level73", "type": "UnsignedDword" }, - { "field": "level74", "type": "UnsignedDword" }, - { "field": "level75", "type": "UnsignedDword" }, - { "field": "level76", "type": "UnsignedDword" }, - { "field": "level77", "type": "UnsignedDword" }, - { "field": "level78", "type": "UnsignedDword" }, - { "field": "level79", "type": "UnsignedDword" }, - { "field": "level80", "type": "UnsignedDword" }, - { "field": "level81", "type": "UnsignedDword" }, - { "field": "level82", "type": "UnsignedDword" }, - { "field": "level83", "type": "UnsignedDword" }, - { "field": "level84", "type": "UnsignedDword" }, - { "field": "level85", "type": "UnsignedDword" }, - { "field": "level86", "type": "UnsignedDword" }, - { "field": "level87", "type": "UnsignedDword" }, - { "field": "level88", "type": "UnsignedDword" }, - { "field": "level89", "type": "UnsignedDword" }, - { "field": "level90", "type": "UnsignedDword" }, - { "field": "level91", "type": "UnsignedDword" }, - { "field": "level92", "type": "UnsignedDword" }, - { "field": "level93", "type": "UnsignedDword" }, - { "field": "level94", "type": "UnsignedDword" }, - { "field": "level95", "type": "UnsignedDword" }, - { "field": "level96", "type": "UnsignedDword" }, - { "field": "level97", "type": "UnsignedDword" }, - { "field": "level98", "type": "UnsignedDword" }, - { "field": "level99", "type": "UnsignedDword" }, - { "field": "level100", "type": "UnsignedDword" }, - { "field": "level101", "type": "UnsignedDword" }, - { "field": "level102", "type": "UnsignedDword" }, - { "field": "level103", "type": "UnsignedDword" }, - { "field": "level104", "type": "UnsignedDword" }, - { "field": "level105", "type": "UnsignedDword" }, - { "field": "level106", "type": "UnsignedDword" }, - { "field": "level107", "type": "UnsignedDword" }, - { "field": "level108", "type": "UnsignedDword" }, - { "field": "level109", "type": "UnsignedDword" }, - { "field": "level110", "type": "UnsignedDword" }, - { "field": "level111", "type": "UnsignedDword" }, - { "field": "level112", "type": "UnsignedDword" }, - { "field": "level113", "type": "UnsignedDword" }, - { "field": "level114", "type": "UnsignedDword" }, - { "field": "level115", "type": "UnsignedDword" }, - { "field": "level116", "type": "UnsignedDword" }, - { "field": "level117", "type": "UnsignedDword" }, - { "field": "level118", "type": "UnsignedDword" }, - { "field": "level119", "type": "UnsignedDword" }, - { "field": "level120", "type": "UnsignedDword" }, - { "field": "level121", "type": "UnsignedDword" }, - { "field": "level122", "type": "UnsignedDword" }, - { "field": "level123", "type": "UnsignedDword" }, - { "field": "level124", "type": "UnsignedDword" }, - { "field": "level125", "type": "UnsignedDword" }, - { "field": "level126", "type": "UnsignedDword" }, - { "field": "level127", "type": "UnsignedDword" }, - { "field": "level128", "type": "UnsignedDword" }, - { "field": "level129", "type": "UnsignedDword" }, - { "field": "level130", "type": "UnsignedDword" }, - { "field": "level131", "type": "UnsignedDword" }, - { "field": "level132", "type": "UnsignedDword" }, - { "field": "level133", "type": "UnsignedDword" }, - { "field": "level134", "type": "UnsignedDword" }, - { "field": "level135", "type": "UnsignedDword" }, - { "field": "level136", "type": "UnsignedDword" }, - { "field": "level137", "type": "UnsignedDword" }, - { "field": "level138", "type": "UnsignedDword" }, - { "field": "level139", "type": "UnsignedDword" }, - { "field": "level140", "type": "UnsignedDword" }, - { "field": "level141", "type": "UnsignedDword" }, - { "field": "level142", "type": "UnsignedDword" }, - { "field": "level143", "type": "UnsignedDword" }, - { "field": "level144", "type": "UnsignedDword" }, - { "field": "level145", "type": "UnsignedDword" }, - { "field": "level146", "type": "UnsignedDword" }, - { "field": "level147", "type": "UnsignedDword" }, - { "field": "level148", "type": "UnsignedDword" }, - { "field": "level149", "type": "UnsignedDword" }, - { "field": "level150", "type": "UnsignedDword" }, - { "field": "level151", "type": "UnsignedDword" }, - { "field": "level152", "type": "UnsignedDword" }, - { "field": "level153", "type": "UnsignedDword" }, - { "field": "level154", "type": "UnsignedDword" }, - { "field": "level155", "type": "UnsignedDword" }, - { "field": "level156", "type": "UnsignedDword" }, - { "field": "level157", "type": "UnsignedDword" }, - { "field": "level158", "type": "UnsignedDword" }, - { "field": "level159", "type": "UnsignedDword" }, - { "field": "level160", "type": "UnsignedDword" }, - { "field": "level161", "type": "UnsignedDword" }, - { "field": "level162", "type": "UnsignedDword" }, - { "field": "level163", "type": "UnsignedDword" }, - { "field": "level164", "type": "UnsignedDword" }, - { "field": "level165", "type": "UnsignedDword" }, - { "field": "level166", "type": "UnsignedDword" }, - { "field": "level167", "type": "UnsignedDword" }, - { "field": "level168", "type": "UnsignedDword" }, - { "field": "level169", "type": "UnsignedDword" }, - { "field": "level170", "type": "UnsignedDword" }, - { "field": "level171", "type": "UnsignedDword" }, - { "field": "level172", "type": "UnsignedDword" }, - { "field": "level173", "type": "UnsignedDword" }, - { "field": "level174", "type": "UnsignedDword" }, - { "field": "level175", "type": "UnsignedDword" }, - { "field": "level176", "type": "UnsignedDword" }, - { "field": "level177", "type": "UnsignedDword" }, - { "field": "level178", "type": "UnsignedDword" }, - { "field": "level179", "type": "UnsignedDword" }, - { "field": "level180", "type": "UnsignedDword" }, - { "field": "level181", "type": "UnsignedDword" }, - { "field": "level182", "type": "UnsignedDword" }, - { "field": "level183", "type": "UnsignedDword" }, - { "field": "level184", "type": "UnsignedDword" }, - { "field": "level185", "type": "UnsignedDword" }, - { "field": "level186", "type": "UnsignedDword" }, - { "field": "level187", "type": "UnsignedDword" }, - { "field": "level188", "type": "UnsignedDword" }, - { "field": "level189", "type": "UnsignedDword" }, - { "field": "level190", "type": "UnsignedDword" }, - { "field": "level191", "type": "UnsignedDword" }, - { "field": "level192", "type": "UnsignedDword" }, - { "field": "level193", "type": "UnsignedDword" }, - { "field": "level194", "type": "UnsignedDword" }, - { "field": "level195", "type": "UnsignedDword" }, - { "field": "level196", "type": "UnsignedDword" }, - { "field": "level197", "type": "UnsignedDword" }, - { "field": "level198", "type": "UnsignedDword" }, - { "field": "level199", "type": "UnsignedDword" }, - { "field": "level200", "type": "UnsignedDword" }, - { "field": "level201", "type": "UnsignedDword" }, - { "field": "level202", "type": "UnsignedDword" }, - { "field": "level203", "type": "UnsignedDword" }, - { "field": "level204", "type": "UnsignedDword" }, - { "field": "level205", "type": "UnsignedDword" }, - { "field": "level206", "type": "UnsignedDword" }, - { "field": "level207", "type": "UnsignedDword" }, - { "field": "level208", "type": "UnsignedDword" }, - { "field": "level209", "type": "UnsignedDword" }, - { "field": "level210", "type": "UnsignedDword" }, - { "field": "level211", "type": "UnsignedDword" }, - { "field": "level212", "type": "UnsignedDword" }, - { "field": "level213", "type": "UnsignedDword" }, - { "field": "level214", "type": "UnsignedDword" }, - { "field": "level215", "type": "UnsignedDword" }, - { "field": "level216", "type": "UnsignedDword" }, - { "field": "level217", "type": "UnsignedDword" }, - { "field": "level218", "type": "UnsignedDword" }, - { "field": "level219", "type": "UnsignedDword" }, - { "field": "level220", "type": "UnsignedDword" }, - { "field": "level221", "type": "UnsignedDword" }, - { "field": "level222", "type": "UnsignedDword" }, - { "field": "level223", "type": "UnsignedDword" }, - { "field": "level224", "type": "UnsignedDword" }, - { "field": "level225", "type": "UnsignedDword" }, - { "field": "level226", "type": "UnsignedDword" }, - { "field": "level227", "type": "UnsignedDword" }, - { "field": "level228", "type": "UnsignedDword" }, - { "field": "level229", "type": "UnsignedDword" }, - { "field": "level230", "type": "UnsignedDword" }, - { "field": "level231", "type": "UnsignedDword" }, - { "field": "level232", "type": "UnsignedDword" }, - { "field": "level233", "type": "UnsignedDword" }, - { "field": "level234", "type": "UnsignedDword" }, - { "field": "level235", "type": "UnsignedDword" }, - { "field": "level236", "type": "UnsignedDword" }, - { "field": "level237", "type": "UnsignedDword" }, - { "field": "level238", "type": "UnsignedDword" }, - { "field": "level239", "type": "UnsignedDword" }, - { "field": "level240", "type": "UnsignedDword" }, - { "field": "level241", "type": "UnsignedDword" }, - { "field": "level242", "type": "UnsignedDword" }, - { "field": "level243", "type": "UnsignedDword" }, - { "field": "level244", "type": "UnsignedDword" }, - { "field": "level245", "type": "UnsignedDword" }, - { "field": "level246", "type": "UnsignedDword" }, - { "field": "level247", "type": "UnsignedDword" }, - { "field": "level248", "type": "UnsignedDword" }, - { "field": "level249", "type": "UnsignedDword" }, - { "field": "level250", "type": "UnsignedDword" }, - { "field": "level251", "type": "UnsignedDword" }, - { "field": "level252", "type": "UnsignedDword" }, - { "field": "level253", "type": "UnsignedDword" }, - { "field": "level254", "type": "UnsignedDword" } - ], "SaveGameThemesCompleted": [ { "field": "theme0", @@ -7485,7 +6534,7 @@ { "field": "local_player", "type": "OnlinePlayer" }, { "field": "lobby", "type": "OnlineLobby" }, { "field": "lobby_dupe", "type": "OnlineLobby" }, - { "field": "servers", "type": "OnlineServers" }, + { "field": "servers", "type": "Array", "length": 32, "arraytype": "OnlineServer" }, { "field": "unknown40", "type": "UnsignedDword" }, { "field": "unknown41", "type": "UnsignedDword" }, { "field": "unknown42", "type": "DataPointer" }, @@ -7510,40 +6559,6 @@ "vectortype": "UnsignedQword" } ], - "OnlineServers": [ - { "field": "server1", "type": "OnlineServer" }, - { "field": "server2", "type": "OnlineServer" }, - { "field": "server3", "type": "OnlineServer" }, - { "field": "server4", "type": "OnlineServer" }, - { "field": "server5", "type": "OnlineServer" }, - { "field": "server6", "type": "OnlineServer" }, - { "field": "server7", "type": "OnlineServer" }, - { "field": "server8", "type": "OnlineServer" }, - { "field": "server9", "type": "OnlineServer" }, - { "field": "server10", "type": "OnlineServer" }, - { "field": "server11", "type": "OnlineServer" }, - { "field": "server12", "type": "OnlineServer" }, - { "field": "server13", "type": "OnlineServer" }, - { "field": "server14", "type": "OnlineServer" }, - { "field": "server15", "type": "OnlineServer" }, - { "field": "server16", "type": "OnlineServer" }, - { "field": "server17", "type": "OnlineServer" }, - { "field": "server18", "type": "OnlineServer" }, - { "field": "server19", "type": "OnlineServer" }, - { "field": "server20", "type": "OnlineServer" }, - { "field": "server21", "type": "OnlineServer" }, - { "field": "server22", "type": "OnlineServer" }, - { "field": "server23", "type": "OnlineServer" }, - { "field": "server24", "type": "OnlineServer" }, - { "field": "server25", "type": "OnlineServer" }, - { "field": "server26", "type": "OnlineServer" }, - { "field": "server27", "type": "OnlineServer" }, - { "field": "server28", "type": "OnlineServer" }, - { "field": "server29", "type": "OnlineServer" }, - { "field": "server30", "type": "OnlineServer" }, - { "field": "server31", "type": "OnlineServer" }, - { "field": "server32", "type": "OnlineServer" } - ], "OnlineServer": [ { "field": "sockaddr", "type": "SockAddrIn" }, { "field": "unknown_query", "type": "Qword" }, @@ -7665,25 +6680,15 @@ { "field": "unknown_mount_ralated", "type": "Word" }, { "field": "collected_money", - "type": "TruncatedEntityDBIDArray", - "comment": "512 slots in total" - }, - { - "field": "skip1", - "type": "Skip", - "offset": 2008, - "comment": "the remaining 502 slots" + "type": "Array", + "length": 512, + "arraytype": "EntityDBID" }, { "field": "collected_money_values", - "type": "TruncatedUnsignedDwordArray", - "comment": "512 slots in total" - }, - { - "field": "skip2", - "type": "Skip", - "offset": 2008, - "comment": "the remaining 502 slots" + "type": "Array", + "length": 512, + "arraytype": "UnsignedDword" }, { "field": "collected_money_count", @@ -7692,144 +6697,32 @@ }, { "field": "killed_enemies", - "type": "TruncatedEntityDBIDArray", - "comment": "256 slots in total" - }, - { - "field": "skip3", - "type": "Skip", - "offset": 984, - "comment": "the remaining 246 slots" + "type": "Array", + "length": 256, + "arraytype": "EntityDBID" }, { "field": "kills_level", "type": "UnsignedDword" }, { "field": "kills_total", "type": "UnsignedDword" }, - { "field": "companion1_held_item_metadata", "type": "Word" }, - { "field": "companion2_held_item_metadata", "type": "Word" }, - { "field": "companion3_held_item_metadata", "type": "Word" }, - { "field": "companion4_held_item_metadata", "type": "Word" }, - { "field": "companion5_held_item_metadata", "type": "Word" }, - { "field": "companion6_held_item_metadata", "type": "Word" }, - { "field": "companion7_held_item_metadata", "type": "Word" }, - { "field": "companion8_held_item_metadata", "type": "Word" }, - { "field": "companion1_poison_tick_timer", "type": "Word" }, - { "field": "companion2_poison_tick_timer", "type": "Word" }, - { "field": "companion3_poison_tick_timer", "type": "Word" }, - { "field": "companion4_poison_tick_timer", "type": "Word" }, - { "field": "companion5_poison_tick_timer", "type": "Word" }, - { "field": "companion6_poison_tick_timer", "type": "Word" }, - { "field": "companion7_poison_tick_timer", "type": "Word" }, - { "field": "companion8_poison_tick_timer", "type": "Word" }, - { "field": "companion1", "type": "EntityDBID" }, - { "field": "companion2", "type": "EntityDBID" }, - { "field": "companion3", "type": "EntityDBID" }, - { "field": "companion4", "type": "EntityDBID" }, - { "field": "companion5", "type": "EntityDBID" }, - { "field": "companion6", "type": "EntityDBID" }, - { "field": "companion7", "type": "EntityDBID" }, - { "field": "companion8", "type": "EntityDBID" }, - { "field": "companion1_held_item", "type": "EntityDBID" }, - { "field": "companion2_held_item", "type": "EntityDBID" }, - { "field": "companion3_held_item", "type": "EntityDBID" }, - { "field": "companion4_held_item", "type": "EntityDBID" }, - { "field": "companion5_held_item", "type": "EntityDBID" }, - { "field": "companion6_held_item", "type": "EntityDBID" }, - { "field": "companion7_held_item", "type": "EntityDBID" }, - { "field": "companion8_held_item", "type": "EntityDBID" }, - { "field": "companion1_trust", "type": "UnsignedByte" }, - { "field": "companion2_trust", "type": "UnsignedByte" }, - { "field": "companion3_trust", "type": "UnsignedByte" }, - { "field": "companion4_trust", "type": "UnsignedByte" }, - { "field": "companion5_trust", "type": "UnsignedByte" }, - { "field": "companion6_trust", "type": "UnsignedByte" }, - { "field": "companion7_trust", "type": "UnsignedByte" }, - { "field": "companion8_trust", "type": "UnsignedByte" }, + { "field": "companion_held_item_metadata", "type": "Array", "length": 8, "arraytype": "UnsignedWord" }, + { "field": "companion_poison_tick_timer", "type": "Array", "length": 8, "arraytype": "UnsignedWord" }, + { "field": "companion", "type": "Array", "length": 8, "arraytype": "EntityDBID" }, + { "field": "companion_held_item", "type": "Array", "length": 8, "arraytype": "EntityDBID" }, + { "field": "companion_trust", "type": "Array", "length": 8, "arraytype": "UnsignedByte" }, { "field": "companion_count", "type": "UnsignedByte" }, - { - "field": "companion1_health", - "type": "UnsignedByte", - "comment": "only when exiting the level" - }, - { "field": "companion2_health", "type": "UnsignedByte" }, - { "field": "companion3_health", "type": "UnsignedByte" }, - { "field": "companion4_health", "type": "UnsignedByte" }, - { "field": "companion5_health", "type": "UnsignedByte" }, - { "field": "companion6_health", "type": "UnsignedByte" }, - { "field": "companion7_health", "type": "UnsignedByte" }, - { "field": "companion8_health", "type": "UnsignedByte" }, - { "field": "companion1_cursed", "type": "Bool" }, - { "field": "companion2_cursed", "type": "Bool" }, - { "field": "companion3_cursed", "type": "Bool" }, - { "field": "companion4_cursed", "type": "Bool" }, - { "field": "companion5_cursed", "type": "Bool" }, - { "field": "companion6_cursed", "type": "Bool" }, - { "field": "companion7_cursed", "type": "Bool" }, - { "field": "companion8_cursed", "type": "Bool" }, + { "field": "companion_health", "type": "Array", "length": 8, "arraytype": "UnsignedByte" }, + { "field": "companion_cursed", "type": "Array", "length": 8, "arraytype": "Bool" }, { "field": "padding1", "type": "Byte" }, { "field": "padding2", "type": "Byte" }, { "field": "padding3", "type": "Byte" }, { "field": "acquired_powerups", - "type": "AquiredPowerups", + "type": "Array", + "length": 30, + "arraytype": "EntityDBID", "comment": "assigned during level transition, so they can be applied to the newly created player entity in the next level" }, { "field": "collected_money_total", "type": "Dword" } ], - "AquiredPowerups": [ - { "field": "slot_1", "type": "EntityDBID" }, - { "field": "slot_2", "type": "EntityDBID" }, - { "field": "slot_3", "type": "EntityDBID" }, - { "field": "slot_4", "type": "EntityDBID" }, - { "field": "slot_5", "type": "EntityDBID" }, - { "field": "slot_6", "type": "EntityDBID" }, - { "field": "slot_7", "type": "EntityDBID" }, - { "field": "slot_8", "type": "EntityDBID" }, - { "field": "slot_9", "type": "EntityDBID" }, - { "field": "slot_10", "type": "EntityDBID" }, - { "field": "slot_11", "type": "EntityDBID" }, - { "field": "slot_12", "type": "EntityDBID" }, - { "field": "slot_13", "type": "EntityDBID" }, - { "field": "slot_14", "type": "EntityDBID" }, - { "field": "slot_15", "type": "EntityDBID" }, - { "field": "slot_16", "type": "EntityDBID" }, - { "field": "slot_17", "type": "EntityDBID" }, - { "field": "slot_18", "type": "EntityDBID" }, - { "field": "slot_19", "type": "EntityDBID" }, - { "field": "slot_20", "type": "EntityDBID" }, - { "field": "slot_21", "type": "EntityDBID" }, - { "field": "slot_22", "type": "EntityDBID" }, - { "field": "slot_23", "type": "EntityDBID" }, - { "field": "slot_24", "type": "EntityDBID" }, - { "field": "slot_25", "type": "EntityDBID" }, - { "field": "slot_26", "type": "EntityDBID" }, - { "field": "slot_27", "type": "EntityDBID" }, - { "field": "slot_28", "type": "EntityDBID" }, - { "field": "slot_29", "type": "EntityDBID" }, - { "field": "slot_30", "type": "EntityDBID" } - ], - "TruncatedEntityDBIDArray": [ - { "field": "slot_1", "type": "EntityDBID" }, - { "field": "slot_2", "type": "EntityDBID" }, - { "field": "slot_3", "type": "EntityDBID" }, - { "field": "slot_4", "type": "EntityDBID" }, - { "field": "slot_5", "type": "EntityDBID" }, - { "field": "slot_6", "type": "EntityDBID" }, - { "field": "slot_7", "type": "EntityDBID" }, - { "field": "slot_8", "type": "EntityDBID" }, - { "field": "slot_9", "type": "EntityDBID" }, - { "field": "slot_10", "type": "EntityDBID" } - ], - "TruncatedUnsignedDwordArray": [ - { "field": "slot_1", "type": "UnsignedDword" }, - { "field": "slot_2", "type": "UnsignedDword" }, - { "field": "slot_3", "type": "UnsignedDword" }, - { "field": "slot_4", "type": "UnsignedDword" }, - { "field": "slot_5", "type": "UnsignedDword" }, - { "field": "slot_6", "type": "UnsignedDword" }, - { "field": "slot_7", "type": "UnsignedDword" }, - { "field": "slot_8", "type": "UnsignedDword" }, - { "field": "slot_9", "type": "UnsignedDword" }, - { "field": "slot_10", "type": "UnsignedDword" } - ], "StdMap": [ { "field": "_Myhead", @@ -8113,21 +7006,16 @@ "offset": 86680, "comment": "grid_entities 126*86*8 minus the one above" }, - { "field": "entities_overlaping_grid_begin", "type": "Qword" }, + { "field": "entities_overlaping_grid_begin", "type": "EntityList" }, { "field": "skip7", "type": "Skip", - "offset": 260056, + "offset": 260040, "comment": "126*86*EntityList (minus the Qword above)" }, { "field": "unknown_entities2", "type": "EntityList" }, - { "field": "entities_by_draw_depth_0", "type": "EntityList" }, - { - "field": "skip8", - "type": "Skip", - "offset": 1272, - "comment": "entities_by_draw_depth 53 * EntityList (minus one above)" - }, + { "field": "entities_by_draw_depth", "type": "Array", "length": 53, "arraytype": "EntityList" }, + { "field": "unknown_entities2a", "type": "EntityList" }, { "field": "unknown_entities3", "type": "EntityList", @@ -8169,12 +7057,12 @@ "EntityList": [ { "field": "entities", - "type": "EntityListPointer", + "type": "DataPointer", "comment": "Entity**" }, { "field": "udis", - "type": "EntityUIDsListPointer", + "type": "DataPointer", "comment": "int32_t*" }, { "field": "capacity", "type": "UnsignedDword" }, @@ -8189,30 +7077,6 @@ { "field": "size", "type": "UnsignedByte" }, { "field": "cap", "type": "UnsignedByte" } ], - "EntityListPointer": [ - { "field": "entity_1", "type": "EntityPointer" }, - { "field": "entity_2", "type": "EntityPointer" }, - { "field": "entity_3", "type": "EntityPointer" }, - { "field": "entity_4", "type": "EntityPointer" }, - { "field": "entity_5", "type": "EntityPointer" }, - { "field": "entity_6", "type": "EntityPointer" }, - { "field": "entity_7", "type": "EntityPointer" }, - { "field": "entity_8", "type": "EntityPointer" }, - { "field": "entity_9", "type": "EntityPointer" }, - { "field": "entity_10", "type": "EntityPointer" } - ], - "EntityUIDsListPointer": [ - { "field": "entity_uid_1", "type": "EntityUID" }, - { "field": "entity_uid_2", "type": "EntityUID" }, - { "field": "entity_uid_3", "type": "EntityUID" }, - { "field": "entity_uid_4", "type": "EntityUID" }, - { "field": "entity_uid_5", "type": "EntityUID" }, - { "field": "entity_uid_6", "type": "EntityUID" }, - { "field": "entity_uid_7", "type": "EntityUID" }, - { "field": "entity_uid_8", "type": "EntityUID" }, - { "field": "entity_uid_9", "type": "EntityUID" }, - { "field": "entity_uid_10", "type": "EntityUID" } - ], "StateItemsPointer": [ { "field": "leader", diff --git a/src/Configuration.cpp b/src/Configuration.cpp index ec1c165..e884ed4 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -143,6 +143,7 @@ namespace S2Plugin {MemoryFieldType::CharacterDBID, "CharacterDBID", "uint8_t", "CharacterDBID", 1, false}, {MemoryFieldType::VirtualFunctionTable, "VirtualFunctionTable", "size_t*", "VirtualFunctionTable", 8, true}, {MemoryFieldType::IPv4Address, "IPv4Address", "uint32_t", "IPv4Address", 4, false}, + {MemoryFieldType::Array, "Array", "", "Array", 0, false}, // Other //{MemoryFieldType::EntitySubclass, "", "", "", 0}, //{MemoryFieldType::DefaultStructType, "", "", "", 0}, @@ -409,6 +410,52 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman } break; } + case MemoryFieldType::UTF16StringFixedSize: + { + if (field.contains("length")) + { + memField.numberOfElements = field["length"].get(); + memField.size = memField.numberOfElements * 2; + break; + } + else if (memField.size == 0) + throw std::runtime_error("Missing `lenght` or `offset` parameter for UTF16StringFixedSize (" + struct_name + "." + memField.name + ")"); + + memField.numberOfElements = memField.size / 2; + memField.name += "[" + std::to_string(memField.numberOfElements) + "]"; + break; + } + case MemoryFieldType::UTF8StringFixedSize: + { + if (field.contains("length")) + memField.size = field["length"].get(); + + if (memField.size == 0) + throw std::runtime_error("Missing valid `lenght` or `offset` parameter for UTF8StringFixedSize (" + struct_name + "." + memField.name + ")"); + + memField.numberOfElements = memField.size; + memField.name += "[" + std::to_string(memField.numberOfElements) + "]"; + break; + } + case MemoryFieldType::Array: + { + if (field.contains("length")) + { + memField.numberOfElements = field["length"].get(); + if (memField.numberOfElements == 0) + throw std::runtime_error("Size 0 not allowed for Array type (" + struct_name + "." + memField.name + ")"); + } + else + throw std::runtime_error("Missing `length` parameter for Array (" + struct_name + "." + memField.name + ")"); + + if (field.contains("arraytype")) + memField.firstParameterType = field["arraytype"].get(); + else + throw std::runtime_error("Missing `arraytype` parameter for Array (" + struct_name + "." + memField.name + ")"); + + memField.name += "[" + std::to_string(memField.numberOfElements) + "]"; + break; + } case MemoryFieldType::DefaultStructType: memField.jsonName = fieldTypeStr; break; @@ -566,7 +613,7 @@ const std::vector& S2Plugin::Configuration::typeFields(co auto it = mTypeFieldsMain.find(type); if (it == mTypeFieldsMain.end()) { - // no error since we can use this to check if type is a struct + // no error since we can use this to check if type is a struct (have fields) // dprintf("unknown key requested in Configuration::typeFields() (t=%s id=%d)\n", gsMemoryFieldType.at(type).display_name.data(), type); static std::vector empty; // just to return valid object return empty; @@ -794,8 +841,11 @@ size_t S2Plugin::MemoryField::get_size() const if (size == 0) { - // no entity sub class, shouldn't be needed - + if (type == MemoryFieldType::Array) + { + const_cast(this)->size = numberOfElements * Configuration::get()->getTypeSize(firstParameterType); + return size; + } if (jsonName.empty()) { size_t new_size = 0; @@ -806,7 +856,6 @@ size_t S2Plugin::MemoryField::get_size() const const_cast(this)->size = new_size; return size; } - const_cast(this)->size = Configuration::get()->getTypeSize(jsonName, type == MemoryFieldType::EntitySubclass); } return size; @@ -898,6 +947,7 @@ uintptr_t S2Plugin::Configuration::offsetForField(MemoryFieldType type, std::str uintptr_t S2Plugin::Configuration::offsetForField(const std::vector& fields, std::string_view fieldUID, uintptr_t addr) const { + // [Known Issue]: can't get element from an Array bool last = false; size_t currentDelimiter = fieldUID.find('.'); @@ -944,3 +994,22 @@ bool S2Plugin::Configuration::isPointerType(MemoryFieldType type) return it->second.isPointer; } + +S2Plugin::MemoryField S2Plugin::Configuration::nameToMemoryField(const std::string& name) const +{ + MemoryField field; + auto type = getBuiltInType(name); + if (type == MemoryFieldType::None) + { + field.type = MemoryFieldType::DefaultStructType; + field.jsonName = name; + field.isPointer = isPermanentPointer(name); + } + else + { + field.type = type; + field.isPointer = isPointerType(type); + field.size = getBuiltInTypeSize(type); + } + return field; +} diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 6eed66b..446cd26 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -73,16 +73,16 @@ QTreeView::branch:open:has-children:has-siblings {\ void S2Plugin::TreeViewMemoryFields::addMemoryFields(const std::vector& fields, const std::string& mainName, uintptr_t structAddr, size_t initialDelta, uint8_t deltaPrefixCount, QStandardItem* parent) { - size_t currentOffset = structAddr; + size_t currentAddr = structAddr; size_t currentDelta = initialDelta; for (auto& field : fields) { - addMemoryField(field, mainName + "." + field.name, currentOffset, currentDelta, deltaPrefixCount, parent); + addMemoryField(field, mainName + "." + field.name, currentAddr, currentDelta, deltaPrefixCount, parent); auto size = field.get_size(); currentDelta += size; if (structAddr != 0) - currentOffset += size; + currentAddr += size; } } @@ -167,6 +167,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& uint8_t flags = 0; QStandardItem* returnField = nullptr; + auto config = Configuration::get(); switch (field.type) { case MemoryFieldType::Skip: @@ -258,8 +259,8 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& } auto flagFieldItem = createAndInsertItem(flagField, fieldNameOverride + "." + flagField.name, flagsParent, 0, showDelta); flagFieldItem->setData(x, gsRoleFlagIndex); - auto flagName = Configuration::get()->flagTitle(field.firstParameterType, x); - QString realFlagName = QString::fromStdString(flagName.empty() ? Configuration::get()->flagTitle("unknown", x) : flagName); // TODO: don't show unknown unless it was chosen in settings + auto flagName = config->flagTitle(field.firstParameterType, x); + QString realFlagName = QString::fromStdString(flagName.empty() ? config->flagTitle("unknown", x) : flagName); // TODO: don't show unknown unless it was chosen in settings flagsParent->child(x - 1, gsColValue)->setData(realFlagName, Qt::DisplayRole); } @@ -269,7 +270,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& case MemoryFieldType::UndeterminedThemeInfoPointer: { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); - addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct("ThemeInfoPointer"), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); + addMemoryFields(config->typeFieldsOfDefaultStruct("ThemeInfoPointer"), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); break; } case MemoryFieldType::StdVector: @@ -277,9 +278,9 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); returnField->setData(QVariant::fromValue(field.firstParameterType), gsRoleStdContainerFirstParameterType); if (field.isPointer) - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); + addMemoryFields(config->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); else - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); + addMemoryFields(config->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } @@ -289,9 +290,9 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& returnField->setData(QVariant::fromValue(field.firstParameterType), gsRoleStdContainerFirstParameterType); returnField->setData(QVariant::fromValue(field.secondParameterType), gsRoleStdContainerSecondParameterType); if (field.isPointer) - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); + addMemoryFields(config->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); else - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); + addMemoryFields(config->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } @@ -299,16 +300,36 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& { returnField = createAndInsertItem(field, fieldNameOverride, parent, 0); returnField->setData(memoryAddress, gsRoleMemoryAddress); - addMemoryFields(Configuration::get()->typeFieldsOfEntitySubclass(field.jsonName), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); + addMemoryFields(config->typeFieldsOfEntitySubclass(field.jsonName), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); + break; + } + case MemoryFieldType::Array: + { + returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); + returnField->setData(QVariant::fromValue(field.firstParameterType), gsRoleStdContainerFirstParameterType); + returnField->setData(field.numberOfElements, gsRoleSize); + if (field.numberOfElements <= 10) // TODO: get the number from settings when done + { + MemoryField index = config->nameToMemoryField(field.firstParameterType); + if (field.isPointer) + delta = 0; + + for (size_t idx = 0; idx < field.numberOfElements; ++idx) + { + index.name = "index_" + std::to_string(idx); + addMemoryField(index, fieldNameOverride + index.name, field.isPointer ? 0 : memoryAddress, delta, field.isPointer ? deltaPrefixCount + 1 : deltaPrefixCount, returnField); + delta += index.get_size(); + } + } break; } case MemoryFieldType::DefaultStructType: { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); if (field.isPointer) - addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct(field.jsonName), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); + addMemoryFields(config->typeFieldsOfDefaultStruct(field.jsonName), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); else - addMemoryFields(Configuration::get()->typeFieldsOfDefaultStruct(field.jsonName), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); + addMemoryFields(config->typeFieldsOfDefaultStruct(field.jsonName), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } @@ -316,9 +337,9 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); if (field.isPointer) - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); + addMemoryFields(config->typeFields(field.type), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); else - addMemoryFields(Configuration::get()->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); + addMemoryFields(config->typeFields(field.type), fieldNameOverride, memoryAddress, delta, deltaPrefixCount, returnField); break; } @@ -1888,6 +1909,19 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::Array: + { + if (!itemField->hasChildren()) + { + if (valueMemoryOffset == 0) + itemValue->setData({}, Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); + + break; + } + [[fallthrough]]; + } case MemoryFieldType::DefaultStructType: { if (isExpanded(itemField->index())) @@ -2237,6 +2271,22 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } break; } + case MemoryFieldType::Array: + { + auto mainField = index.sibling(index.row(), gsColField); + if (!mainField.child(0, 0).isValid()) + { + auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (rawValue == 0) + return; + + auto typeName = qvariant_cast(mainField.data(gsRoleStdContainerFirstParameterType)); + + getToolbar()->showArray(rawValue, mainField.data(Qt::DisplayRole).toString().toStdString(), typeName, mainField.data(gsRoleSize).toULongLong()); + break; + } + [[fallthrough]]; + } case MemoryFieldType::DefaultStructType: case MemoryFieldType::EntitySubclass: case MemoryFieldType::Dummy: diff --git a/src/Views/ViewStruct.cpp b/src/Views/ViewStruct.cpp index 588d246..f0f49cc 100644 --- a/src/Views/ViewStruct.cpp +++ b/src/Views/ViewStruct.cpp @@ -44,6 +44,26 @@ S2Plugin::ViewStruct::ViewStruct(uintptr_t address, const std::vectortoggleAutoRefresh(true); } +S2Plugin::ViewArray::ViewArray(uintptr_t address, MemoryField field, size_t num, const std::string name, QWidget* parent) : ViewStruct(address, {}, name, parent) +{ + size_t currentAddr = address; + size_t currentDelta = 0; + size_t size = field.get_size(); + field.name = "index_000"; + + for (size_t idx = 0; idx < num; ++idx) + { + auto indexString = std::to_string(idx); + auto itr = 9 - indexString.length(); // 9 - lenght of "index_000" + field.name.replace(itr, indexString.length(), indexString); + + mMainTreeView->addMemoryField(field, name + "." + field.name, currentAddr, currentDelta, 0, nullptr); + currentDelta += size; + if (address != 0) + currentAddr += size; + } +} + QSize S2Plugin::ViewStruct::sizeHint() const { return QSize(750, 1050); diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index b497259..c4b3c16 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -184,6 +184,15 @@ void S2Plugin::ViewToolbar::showEntity(uintptr_t address) win->setAttribute(Qt::WA_DeleteOnClose); } +void S2Plugin::ViewToolbar::showArray(uintptr_t address, std::string name, std::string arrayTypeName, size_t length) +{ + auto field = Configuration::get()->nameToMemoryField(arrayTypeName); + auto w = new ViewArray(address, std::move(field), length, arrayTypeName + " " + name); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + // // slots: // From c305a23ac5f0ca7bbeefedd801df55d1e5cd2e7f Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Thu, 30 May 2024 20:31:34 +0200 Subject: [PATCH 02/20] implement Matrix, slightly change Array implementation --- include/Configuration.h | 13 +++- include/Views/ViewStruct.h | 7 ++- include/Views/ViewToolbar.h | 1 + include/read_helpers.h | 6 +- resources/Spelunky2.json | 68 +++++++++----------- src/Configuration.cpp | 37 ++++++++++- src/QtHelpers/TreeViewMemoryFields.cpp | 87 ++++++++++++++++++++++++-- src/QtHelpers/WidgetSpelunkyLevel.cpp | 1 - src/Views/ViewStruct.cpp | 38 ++++++----- src/Views/ViewToolbar.cpp | 11 +++- 10 files changed, 197 insertions(+), 72 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 7fdcac5..83266cb 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -50,7 +50,8 @@ namespace S2Plugin constexpr uint16_t gsRoleStdContainerFirstParameterType = Qt::UserRole + 8; constexpr uint16_t gsRoleStdContainerSecondParameterType = Qt::UserRole + 9; constexpr uint16_t gsRoleSize = Qt::UserRole + 10; - constexpr uint16_t gsRoleEntityAddress = Qt::UserRole + 11; // for entity uid to not look for the uid twice + constexpr uint16_t gsRoleColumns = Qt::UserRole + 11; // for Matrix + constexpr uint16_t gsRoleEntityAddress = Qt::UserRole + 12; // for entity uid to not look for the uid twice // new types need to be added to // - the MemoryFieldType enum @@ -127,6 +128,7 @@ namespace S2Plugin IPv4Address, Double, Array, + Matrix, }; struct VirtualFunction @@ -149,13 +151,21 @@ namespace S2Plugin // jsonName only if applicable: if a type is not a MemoryFieldType, but fully defined in the json file // then save its name so we can compare later std::string jsonName; + // parameter types for stuff like vectors, maps etc. std::string firstParameterType; std::string secondParameterType; std::string comment; // size in bytes size_t get_size() const; + union + { // length, size of array etc. size_t numberOfElements{0}; + // row count for matrix + size_t rows; + }; + // column count for matrix + size_t columns{0}; // For checking duplicate names bool operator==(const MemoryField& other) const @@ -166,6 +176,7 @@ namespace S2Plugin private: size_t size{0}; friend class Configuration; + friend class ViewMatrix; }; struct RoomCode diff --git a/include/Views/ViewStruct.h b/include/Views/ViewStruct.h index 1b32277..382f7d9 100644 --- a/include/Views/ViewStruct.h +++ b/include/Views/ViewStruct.h @@ -26,6 +26,11 @@ namespace S2Plugin class ViewArray : public ViewStruct { public: - ViewArray(uintptr_t address, MemoryField field, size_t num, const std::string name, QWidget* parent = nullptr); + ViewArray(uintptr_t address, std::string arrayTypeName, size_t num, std::string name, QWidget* parent = nullptr); + }; + class ViewMatrix : public ViewStruct + { + public: + ViewMatrix(uintptr_t address, std::string arrayTypeName, size_t row, size_t col, std::string name, QWidget* parent = nullptr); }; } // namespace S2Plugin diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index d3e5fbc..aa36b7e 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -27,6 +27,7 @@ namespace S2Plugin void showJournalPage(uintptr_t address); void showLevelGen(uintptr_t address); void showArray(uintptr_t address, std::string name, std::string arrayTypeName, size_t length); + void showMatrix(uintptr_t address, std::string name, std::string arrayTypeName, size_t rows, size_t columns); public slots: ViewEntityDB* showEntityDB(); diff --git a/include/read_helpers.h b/include/read_helpers.h index 83eb198..6432932 100644 --- a/include/read_helpers.h +++ b/include/read_helpers.h @@ -7,7 +7,7 @@ namespace S2Plugin { template - T Read(uintptr_t addr) + [[nodiscard]] T Read(uintptr_t addr) { T x{}; Script::Memory::Read(addr, &x, sizeof(T), nullptr); @@ -15,7 +15,7 @@ namespace S2Plugin } template - std::basic_string ReadConstBasicString(uintptr_t addr) + [[nodiscard]] std::basic_string ReadConstBasicString(uintptr_t addr) { if (addr == 0) return {}; @@ -38,7 +38,7 @@ namespace S2Plugin dprintf("[ReadConstBasicString] read (bytes): %d expected: %d\n", read_size, size * char_size); return str; } - inline std::string ReadConstString(uintptr_t addr) + [[nodiscard]] inline std::string ReadConstString(uintptr_t addr) { return ReadConstBasicString(addr); } diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index c851312..09bfb98 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -6950,48 +6950,36 @@ }, { "field": "entities_by_region1", - "type": "EntityList", + "type": "Matrix", + "row": 31, + "col": 21, + "matrixtype": "EntityList", "comment": "31*21*EntityList" }, - { - "field": "skip2", - "type": "Skip", - "offset": 15600, - "comment": "31*21*EntityList (minus on above)" - }, { "field": "entities_by_region2", - "type": "EntityList", + "type": "Matrix", + "row": 31, + "col": 21, + "matrixtype": "EntityList", "comment": "31*21*EntityList" }, - { - "field": "skip3", - "type": "Skip", - "offset": 15600, - "comment": "31*21*EntityList (minus on above)" - }, { "field": "entities_by_region3", - "type": "EntityList", + "type": "Matrix", + "row": 31, + "col": 21, + "matrixtype": "EntityList", "comment": "31*21*EntityList" }, - { - "field": "skip4", - "type": "Skip", - "offset": 15600, - "comment": "31*21*EntityList (minus on above)" - }, { "field": "entities_by_region4", - "type": "EntityList", + "type": "Matrix", + "row": 31, + "col": 21, + "matrixtype": "EntityList", "comment": "31*21*EntityList" }, - { - "field": "skip5", - "type": "Skip", - "offset": 15600, - "comment": "31*21*EntityList (minus on above)" - }, { "field": "entity_regions", "type": "StdMap", @@ -6999,18 +6987,20 @@ "valuetype": "EntityRegions", "comment": "key is uid, all entities except FX, FLOOR, DECORATION, BG, SHADOW and LOGICAL" }, - { "field": "grid_entities_begin", "type": "Qword" }, { - "field": "skip6", - "type": "Skip", - "offset": 86680, + "field": "grid_entities", + "type": "Matrix", + "row": 126, + "col": 86, + "matrixtype": "EntityPointer", "comment": "grid_entities 126*86*8 minus the one above" }, - { "field": "entities_overlaping_grid_begin", "type": "EntityList" }, { - "field": "skip7", - "type": "Skip", - "offset": 260040, + "field": "entities_overlaping_grid", + "type": "Matrix", + "row": 126, + "col": 86, + "matrixtype": "EntityList", "comment": "126*86*EntityList (minus the Qword above)" }, { "field": "unknown_entities2", "type": "EntityList" }, @@ -8069,14 +8059,14 @@ { "field": "unknown1", "type": "DataPointer", - "comment": "some sort of array?" + "comment": "some sort of container" }, { - "field": "unknown2", + "field": "size", "type": "Dword", "comment": "related to the level size?" }, - { "field": "unknown3", "type": "Dword" }, + { "field": "cap", "type": "Dword" }, { "field": "unknown4", "type": "DataPointer" }, { "field": "unknown5", "type": "DataPointer" }, { "field": "unknown6", "type": "DataPointer" }, diff --git a/src/Configuration.cpp b/src/Configuration.cpp index e884ed4..4027d4c 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -144,6 +144,7 @@ namespace S2Plugin {MemoryFieldType::VirtualFunctionTable, "VirtualFunctionTable", "size_t*", "VirtualFunctionTable", 8, true}, {MemoryFieldType::IPv4Address, "IPv4Address", "uint32_t", "IPv4Address", 4, false}, {MemoryFieldType::Array, "Array", "", "Array", 0, false}, + {MemoryFieldType::Matrix, "Matrix", "", "Matrix", 0, false}, // Other //{MemoryFieldType::EntitySubclass, "", "", "", 0}, //{MemoryFieldType::DefaultStructType, "", "", "", 0}, @@ -443,7 +444,7 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman { memField.numberOfElements = field["length"].get(); if (memField.numberOfElements == 0) - throw std::runtime_error("Size 0 not allowed for Array type (" + struct_name + "." + memField.name + ")"); + throw std::runtime_error("Length 0 not allowed for Array type (" + struct_name + "." + memField.name + ")"); } else throw std::runtime_error("Missing `length` parameter for Array (" + struct_name + "." + memField.name + ")"); @@ -453,7 +454,32 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman else throw std::runtime_error("Missing `arraytype` parameter for Array (" + struct_name + "." + memField.name + ")"); - memField.name += "[" + std::to_string(memField.numberOfElements) + "]"; + break; + } + case MemoryFieldType::Matrix: + { + if (field.contains("matrixtype")) + memField.firstParameterType = field["matrixtype"].get(); + else + throw std::runtime_error("Missing `matrixtype` parameter for Matrix (" + struct_name + "." + memField.name + ")"); + + if (field.contains("row")) + { + memField.rows = field["row"].get(); + if (memField.rows == 0) + throw std::runtime_error("Size 0 not allowed for Matrix type (" + struct_name + "." + memField.name + ")"); + } + else + throw std::runtime_error("Missing `row` parameter for Matrix (" + struct_name + "." + memField.name + ")"); + + if (field.contains("col")) + { + memField.columns = field["col"].get(); + if (memField.columns == 0) + throw std::runtime_error("Size 0 not allowed for Matrix type (" + struct_name + "." + memField.name + ")"); + } + else + throw std::runtime_error("Missing `col` parameter for Matrix (" + struct_name + "." + memField.name + ")"); break; } case MemoryFieldType::DefaultStructType: @@ -846,6 +872,11 @@ size_t S2Plugin::MemoryField::get_size() const const_cast(this)->size = numberOfElements * Configuration::get()->getTypeSize(firstParameterType); return size; } + if (type == MemoryFieldType::Matrix) + { + const_cast(this)->size = rows * columns * Configuration::get()->getTypeSize(firstParameterType); + return size; + } if (jsonName.empty()) { size_t new_size = 0; @@ -947,7 +978,7 @@ uintptr_t S2Plugin::Configuration::offsetForField(MemoryFieldType type, std::str uintptr_t S2Plugin::Configuration::offsetForField(const std::vector& fields, std::string_view fieldUID, uintptr_t addr) const { - // [Known Issue]: can't get element from an Array + // [Known Issue]: can't get element from an Array or Matrix bool last = false; size_t currentDelimiter = fieldUID.find('.'); diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 446cd26..64bf322 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -147,7 +147,11 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& itemFieldType->setEditable(false); QString typeName = field.isPointer ? "P: " : ""; // add color? - if (field.type == MemoryFieldType::EntitySubclass || field.type == MemoryFieldType::DefaultStructType) + if (field.type == MemoryFieldType::Matrix) + typeName += QString("%1[%2][%3]").arg(QString::fromStdString(field.firstParameterType)).arg(field.rows).arg(field.columns); + else if (field.type == MemoryFieldType::Array) + typeName += QString("%1[%2]").arg(QString::fromStdString(field.firstParameterType)).arg(field.numberOfElements); + else if (field.type == MemoryFieldType::EntitySubclass || field.type == MemoryFieldType::DefaultStructType) typeName += QString::fromStdString(field.jsonName); else if (auto str = Configuration::getTypeDisplayName(field.type); !str.empty()) typeName += QString::fromUtf8(str.data(), static_cast(str.size())); @@ -305,20 +309,71 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& } case MemoryFieldType::Array: { - returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); - returnField->setData(QVariant::fromValue(field.firstParameterType), gsRoleStdContainerFirstParameterType); - returnField->setData(field.numberOfElements, gsRoleSize); - if (field.numberOfElements <= 10) // TODO: get the number from settings when done + if (field.secondParameterType == "#") + returnField = parent; + else + { + returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); + returnField->setData(QVariant::fromValue(field.firstParameterType), gsRoleStdContainerFirstParameterType); + returnField->setData(field.numberOfElements, gsRoleSize); + } + + if (field.numberOfElements <= 30 || field.secondParameterType == "#" || field.secondParameterType == "$") // TODO: get the number from settings when done { MemoryField index = config->nameToMemoryField(field.firstParameterType); + index.name = field.name + '['; + auto initialNameSize = index.name.size(); + if (field.isPointer) delta = 0; for (size_t idx = 0; idx < field.numberOfElements; ++idx) { - index.name = "index_" + std::to_string(idx); + index.name.erase(initialNameSize); + index.name += std::to_string(idx) + ']'; + addMemoryField(index, fieldNameOverride + index.name, field.isPointer ? 0 : memoryAddress, delta, field.isPointer ? deltaPrefixCount + 1 : deltaPrefixCount, returnField); delta += index.get_size(); + if (memoryAddress != 0) + memoryAddress += index.get_size(); + } + } + break; + } + case MemoryFieldType::Matrix: + { + if (field.secondParameterType == "$") + returnField = parent; + else + { + returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); + returnField->setData(QVariant::fromValue(field.firstParameterType), gsRoleStdContainerFirstParameterType); + returnField->setData(field.rows, gsRoleSize); + returnField->setData(field.columns, gsRoleColumns); + } + + if (field.rows <= 30 || field.secondParameterType == "$") // TODO: get the number from settings when done + // columns limit dealt by the array + { + if (field.isPointer) + delta = 0; + + MemoryField row; + row.numberOfElements = field.columns; + row.firstParameterType = field.firstParameterType; + row.secondParameterType = field.secondParameterType; + row.type = MemoryFieldType::Array; + row.name = field.name + '['; + auto initialNameSize = row.name.size(); + + for (size_t idx = 0; idx < field.rows; ++idx) + { + row.name.erase(initialNameSize); + row.name += std::to_string(idx) + ']'; + addMemoryField(row, fieldNameOverride + row.name, field.isPointer ? 0 : memoryAddress, delta, field.isPointer ? deltaPrefixCount + 1 : deltaPrefixCount, returnField); + delta += row.get_size(); + if (memoryAddress != 0) + memoryAddress += row.get_size(); } } break; @@ -1910,6 +1965,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional break; } case MemoryFieldType::Array: + case MemoryFieldType::Matrix: { if (!itemField->hasChildren()) { @@ -2271,6 +2327,25 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } break; } + case MemoryFieldType::Matrix: + { + auto mainField = index.sibling(index.row(), gsColField); + if (!mainField.child(0, 0).isValid()) + { + auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (rawValue == 0) + return; + + auto typeName = qvariant_cast(mainField.data(gsRoleStdContainerFirstParameterType)); + + auto rows = mainField.data(gsRoleSize).toULongLong(); + auto columns = mainField.data(gsRoleColumns).toULongLong(); + + getToolbar()->showMatrix(rawValue, mainField.data(Qt::DisplayRole).toString().toStdString(), typeName, rows, columns); + break; + } + [[fallthrough]]; // can't just fall into DefaultStructType, but it shoudln't matter as array will do the same check and fall futher anyway + } case MemoryFieldType::Array: { auto mainField = index.sibling(index.row(), gsColField); diff --git a/src/QtHelpers/WidgetSpelunkyLevel.cpp b/src/QtHelpers/WidgetSpelunkyLevel.cpp index 979814b..a5771eb 100644 --- a/src/QtHelpers/WidgetSpelunkyLevel.cpp +++ b/src/QtHelpers/WidgetSpelunkyLevel.cpp @@ -63,7 +63,6 @@ void S2Plugin::WidgetSpelunkyLevel::paintEvent(QPaintEvent*) } if (mEntityMasksToPaint != 0) { - StdMap maskMap{layerToDraw == 0 ? mMaskMapAddr.first : mMaskMapAddr.second}; for (uint8_t bit_number = 0; bit_number < mEntityMaskColors.size(); ++bit_number) { if ((mEntityMasksToPaint >> bit_number) & 1) diff --git a/src/Views/ViewStruct.cpp b/src/Views/ViewStruct.cpp index f0f49cc..07956f3 100644 --- a/src/Views/ViewStruct.cpp +++ b/src/Views/ViewStruct.cpp @@ -44,24 +44,30 @@ S2Plugin::ViewStruct::ViewStruct(uintptr_t address, const std::vectortoggleAutoRefresh(true); } -S2Plugin::ViewArray::ViewArray(uintptr_t address, MemoryField field, size_t num, const std::string name, QWidget* parent) : ViewStruct(address, {}, name, parent) +S2Plugin::ViewArray::ViewArray(uintptr_t address, std::string arrayTypeName, size_t num, std::string name, QWidget* parent) + : ViewStruct(0, {}, arrayTypeName + " " + name + '[' + std::to_string(num) + ']', parent) { - size_t currentAddr = address; - size_t currentDelta = 0; - size_t size = field.get_size(); - field.name = "index_000"; - - for (size_t idx = 0; idx < num; ++idx) - { - auto indexString = std::to_string(idx); - auto itr = 9 - indexString.length(); // 9 - lenght of "index_000" - field.name.replace(itr, indexString.length(), indexString); + MemoryField array; + array.name = name; + array.type = MemoryFieldType::Array; + array.firstParameterType = arrayTypeName; + array.secondParameterType = '#'; // just to let it know it should put all the elements in, no array element + array.numberOfElements = num; + mMainTreeView->addMemoryField(array, {}, address, 0); +} - mMainTreeView->addMemoryField(field, name + "." + field.name, currentAddr, currentDelta, 0, nullptr); - currentDelta += size; - if (address != 0) - currentAddr += size; - } +S2Plugin::ViewMatrix::ViewMatrix(uintptr_t address, std::string arrayTypeName, size_t rows, size_t columns, std::string name, QWidget* parent) + : ViewStruct(0, {}, arrayTypeName + " " + name + '[' + std::to_string(rows) + "][" + std::to_string(columns) + ']', parent) +{ + MemoryField matrix; + matrix.name = name; + matrix.type = MemoryFieldType::Matrix; + matrix.firstParameterType = arrayTypeName; + matrix.secondParameterType = '$'; // just to let it know it should put all the elements in, no matrix element + // it can't be # since we still want the array to be placed normally, just no size limit + matrix.rows = rows; + matrix.columns = columns; + mMainTreeView->addMemoryField(matrix, {}, address, 0); } QSize S2Plugin::ViewStruct::sizeHint() const diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index c4b3c16..9ab970e 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -186,8 +186,15 @@ void S2Plugin::ViewToolbar::showEntity(uintptr_t address) void S2Plugin::ViewToolbar::showArray(uintptr_t address, std::string name, std::string arrayTypeName, size_t length) { - auto field = Configuration::get()->nameToMemoryField(arrayTypeName); - auto w = new ViewArray(address, std::move(field), length, arrayTypeName + " " + name); + auto w = new ViewArray(address, std::move(arrayTypeName), length, std::move(name)); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +void S2Plugin::ViewToolbar::showMatrix(uintptr_t address, std::string name, std::string arrayTypeName, size_t rows, size_t columns) +{ + auto w = new ViewMatrix(address, std::move(arrayTypeName), rows, columns, std::move(name)); auto win = mMDIArea->addSubWindow(w); win->setVisible(true); win->setAttribute(Qt::WA_DeleteOnClose); From 6c2d63b5e4e8fcba1807719232dca23c54be5500 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 1 Jun 2024 10:58:36 +0200 Subject: [PATCH 03/20] fix editing float and double value, fix level view since adding array changed variable name, some improvements --- include/Configuration.h | 6 +++++- include/Views/ViewStruct.h | 2 +- src/Configuration.cpp | 6 +++--- src/QtHelpers/DialogEditSimpleValue.cpp | 21 +++++++++++++++++---- src/QtHelpers/WidgetSpelunkyLevel.cpp | 4 ++-- src/Views/ViewEntity.cpp | 7 ++++--- src/Views/ViewStruct.cpp | 2 +- 7 files changed, 33 insertions(+), 15 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 83266cb..09c1354 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -235,7 +235,11 @@ namespace S2Plugin { return std::find(mPointerTypes.begin(), mPointerTypes.end(), type) != mPointerTypes.end(); } - bool isJsonStruct(const std::string type) const + bool isPermanentPointer(const std::string_view type) const + { + return std::find(mPointerTypes.begin(), mPointerTypes.end(), type) != mPointerTypes.end(); + } + bool isJsonStruct(const std::string& type) const { return mTypeFieldsStructs.find(type) != mTypeFieldsStructs.end(); } diff --git a/include/Views/ViewStruct.h b/include/Views/ViewStruct.h index 382f7d9..cffeada 100644 --- a/include/Views/ViewStruct.h +++ b/include/Views/ViewStruct.h @@ -14,7 +14,7 @@ namespace S2Plugin { Q_OBJECT public: - ViewStruct(uintptr_t address, const std::vector& fields, const std::string name, QWidget* parent = nullptr); + ViewStruct(uintptr_t address, const std::vector& fields, const std::string& name, QWidget* parent = nullptr); protected: QSize sizeHint() const override; diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 4027d4c..cc7428f 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -264,7 +264,7 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman memField.name = field["field"].get(); memField.comment = value_or(field, "commment", ""s); memField.type = MemoryFieldType::DefaultStructType; // just initial - std::string fieldTypeStr = field["type"].get(); + std::string_view fieldTypeStr = field["type"].get(); if (isPermanentPointer(fieldTypeStr) || value_or(field, "pointer", false)) { @@ -503,10 +503,10 @@ void S2Plugin::Configuration::processEntitiesJSON(ordered_json& j) for (const auto& [key, jsonValue] : j["entity_class_hierarchy"].items()) { - auto value = jsonValue.get(); + auto value = jsonValue.get(); if (key != value) { - mEntityClassHierarchy[key] = std::move(value); + mEntityClassHierarchy[key] = value; } } for (const auto& [key, jsonValue] : j["default_entity_types"].items()) diff --git a/src/QtHelpers/DialogEditSimpleValue.cpp b/src/QtHelpers/DialogEditSimpleValue.cpp index 444d07e..6edf594 100644 --- a/src/QtHelpers/DialogEditSimpleValue.cpp +++ b/src/QtHelpers/DialogEditSimpleValue.cpp @@ -115,14 +115,27 @@ S2Plugin::DialogEditSimpleValue::DialogEditSimpleValue(const QString& fieldName, break; } case MemoryFieldType::Float: - case MemoryFieldType::Double: { auto spinbox = new QDoubleSpinBox(this); + spinbox->setDecimals(6); + spinbox->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); + QObject::connect(spinbox, static_cast(&QDoubleSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); + uint32_t tmp = Script::Memory::ReadDword(mMemoryAddress); + auto v = reinterpret_cast(tmp); + spinbox->setValue(v); mSpinBox = spinbox; + break; + } + case MemoryFieldType::Double: + { + auto spinbox = new QDoubleSpinBox(this); + spinbox->setDecimals(15); + spinbox->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); QObject::connect(spinbox, static_cast(&QDoubleSpinBox::valueChanged), this, &DialogEditSimpleValue::decValueChanged); size_t tmp = Script::Memory::ReadQword(mMemoryAddress); double v = reinterpret_cast(tmp); spinbox->setValue(v); + mSpinBox = spinbox; break; } } @@ -223,7 +236,7 @@ void S2Plugin::DialogEditSimpleValue::changeBtnClicked() if (obj) // probably not needed but just in case v = obj->value(); - Script::Memory::WriteQword(mMemoryAddress, v); + Script::Memory::WriteQword(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::UnsignedQword: @@ -316,14 +329,14 @@ void S2Plugin::DialogEditSimpleValue::decValueChanged(const QString& text) } case MemoryFieldType::Float: { - float v = text.toFloat(); + float v = QLocale::system().toFloat(text); uint32_t tmp = reinterpret_cast(v); ss << "0x" << std::hex << std::setw(8) << std::setfill('0') << tmp; break; } case MemoryFieldType::Double: { - double v = text.toDouble(); + double v = QLocale::system().toDouble(text); size_t tmp = reinterpret_cast(v); ss << "0x" << std::hex << std::setw(16) << std::setfill('0') << tmp; break; diff --git a/src/QtHelpers/WidgetSpelunkyLevel.cpp b/src/QtHelpers/WidgetSpelunkyLevel.cpp index a5771eb..dbd5529 100644 --- a/src/QtHelpers/WidgetSpelunkyLevel.cpp +++ b/src/QtHelpers/WidgetSpelunkyLevel.cpp @@ -11,8 +11,8 @@ S2Plugin::WidgetSpelunkyLevel::WidgetSpelunkyLevel(uintptr_t main_entity, QWidge auto stateptr = Spelunky2::get()->get_StatePtr(); mMaskMapAddr.first = Configuration::get()->offsetForField(MemoryFieldType::State, "layer0.entities_by_mask", stateptr); mMaskMapAddr.second = Configuration::get()->offsetForField(MemoryFieldType::State, "layer1.entities_by_mask", stateptr); - mGridEntitiesAddr.first = Configuration::get()->offsetForField(MemoryFieldType::State, "layer0.grid_entities_begin", stateptr); - mGridEntitiesAddr.second = Configuration::get()->offsetForField(MemoryFieldType::State, "layer1.grid_entities_begin", stateptr); + mGridEntitiesAddr.first = Configuration::get()->offsetForField(MemoryFieldType::State, "layer0.grid_entities", stateptr); + mGridEntitiesAddr.second = Configuration::get()->offsetForField(MemoryFieldType::State, "layer1.grid_entities", stateptr); // auto offset = Configuration::get()->offsetForField(MemoryFieldType::State, "level_width_rooms", stateptr); // mLevelWidth = Script::Memory::ReadDword(offset) * 10; diff --git a/src/Views/ViewEntity.cpp b/src/Views/ViewEntity.cpp index ccba2aa..0e10118 100644 --- a/src/Views/ViewEntity.cpp +++ b/src/Views/ViewEntity.cpp @@ -217,6 +217,7 @@ void S2Plugin::ViewEntity::interpretAsChanged(const QString& classType) { if (!field.isPointer) { + // note: this will not work with arrays and probably other sutff, in the future prefer to use mMainTreeView if (field.type == MemoryFieldType::DefaultStructType) { self(prefix + field.name + ".", config->typeFieldsOfDefaultStruct(field.jsonName), self); @@ -275,7 +276,6 @@ void S2Plugin::ViewEntity::updateComparedMemoryViewHighlights() // TODO: don't clear tooltip if the interpretAs was not changed, maybe consider adding updateHighlightedField mMemoryComparisonView->clearHighlights(); auto root = qobject_cast(mMainTreeView->model())->invisibleRootItem(); - auto config = Configuration::get(); size_t offset = 0; std::string fieldName; QColor color; @@ -288,7 +288,8 @@ void S2Plugin::ViewEntity::updateComparedMemoryViewHighlights() auto field = parrent->child(idx, gsColField); type = field->data(gsRoleType).value(); bool isPointer = field->data(gsRoleIsPointer).toBool(); - if (!isPointer && (type == MemoryFieldType::DefaultStructType || !config->typeFields(type).empty())) + // [Known Issue]: This may need update if we ever add field types that have children with not actual memory representation + if (!isPointer && field->hasChildren() && type != MemoryFieldType::Flags8 && type != MemoryFieldType::Flags16 && type != MemoryFieldType::Flags32) { self(field, self); continue; @@ -296,7 +297,7 @@ void S2Plugin::ViewEntity::updateComparedMemoryViewHighlights() auto deltaField = parrent->child(idx, gsColMemoryAddressDelta); size_t delta = deltaField->data(gsRoleRawValue).toULongLong(); // get the size by the difference in offset delta - // [Known Issue]: this will fail in getting the correct size if there is a skip element between fields + // TODO: this will fail in getting the correct size if there is a skip element between fields int size = static_cast(delta - offset); if (size != 0) { diff --git a/src/Views/ViewStruct.cpp b/src/Views/ViewStruct.cpp index 07956f3..10d737d 100644 --- a/src/Views/ViewStruct.cpp +++ b/src/Views/ViewStruct.cpp @@ -9,7 +9,7 @@ #include #include -S2Plugin::ViewStruct::ViewStruct(uintptr_t address, const std::vector& fields, const std::string name, QWidget* parent) : QWidget(parent) +S2Plugin::ViewStruct::ViewStruct(uintptr_t address, const std::vector& fields, const std::string& name, QWidget* parent) : QWidget(parent) { setWindowIcon(getCavemanIcon()); setWindowTitle(QString::fromStdString(name)); From 0a284b8776d26b23a7d33aedad2fd85f1f858ab3 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 1 Jun 2024 11:11:48 +0200 Subject: [PATCH 04/20] Implement EntityList view, also display when container is empty (EntityList, StdMap, StdVector) --- CMakeLists.txt | 3 + include/Configuration.h | 9 +- include/Data/EntityList.h | 159 +++++++++++++++++++++++++ include/Data/IDNameList.h | 8 +- include/Views/ViewEntityList.h | 30 +++++ include/Views/ViewToolbar.h | 1 + src/Configuration.cpp | 1 + src/Data/IDNameList.cpp | 2 +- src/QtHelpers/TreeViewMemoryFields.cpp | 74 +++++++++++- src/Views/ViewEntityList.cpp | 69 +++++++++++ src/Views/ViewToolbar.cpp | 9 ++ 11 files changed, 350 insertions(+), 15 deletions(-) create mode 100644 include/Data/EntityList.h create mode 100644 include/Views/ViewEntityList.h create mode 100644 src/Views/ViewEntityList.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3346f0c..d369cd3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,6 +60,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Data/Logger.h include/Data/StdString.h include/Data/StdMap.h + include/Data/EntityList.h include/Views/ViewToolbar.h include/Views/ViewEntityDB.h include/Views/ViewParticleDB.h @@ -77,6 +78,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Views/ViewJournalPage.h include/Views/ViewThreads.h include/Views/ViewStdMap.h + include/Views/ViewEntityList.h include/QtHelpers/StyledItemDelegateHTML.h include/QtHelpers/StyledItemDelegateColorPicker.h include/QtHelpers/TreeViewMemoryFields.h @@ -131,6 +133,7 @@ x64dbg_plugin(${PROJECT_NAME} src/Views/ViewStdMap.cpp src/Views/ViewJournalPage.cpp src/Views/ViewThreads.cpp + src/Views/ViewEntityList.cpp src/QtHelpers/StyledItemDelegateHTML.cpp src/QtHelpers/TreeViewMemoryFields.cpp src/QtHelpers/WidgetMemoryView.cpp diff --git a/include/Configuration.h b/include/Configuration.h index 09c1354..31e9443 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -129,6 +129,7 @@ namespace S2Plugin Double, Array, Matrix, + EntityList, }; struct VirtualFunction @@ -159,8 +160,8 @@ namespace S2Plugin size_t get_size() const; union { - // length, size of array etc. - size_t numberOfElements{0}; + // length, size of array etc. + size_t numberOfElements{0}; // row count for matrix size_t rows; }; @@ -249,7 +250,7 @@ namespace S2Plugin const std::vector>& refTitlesOfField(const std::string& fieldName) const; size_t getTypeSize(const std::string& typeName, bool entitySubclass = false); - const EntityList& entityList() const + const EntityNamesList& entityList() const { return entityNames; }; @@ -295,7 +296,7 @@ namespace S2Plugin void processRoomCodesJSON(nlohmann::ordered_json& json); MemoryField populateMemoryField(const nlohmann::ordered_json& field, const std::string& struct_name); - EntityList entityNames; + EntityNamesList entityNames; ParticleEmittersList particleEmitters; Configuration(); diff --git a/include/Data/EntityList.h b/include/Data/EntityList.h new file mode 100644 index 0000000..3498e3c --- /dev/null +++ b/include/Data/EntityList.h @@ -0,0 +1,159 @@ +#pragma once + +#include "Entity.h" +#include "pluginmain.h" +#include "read_helpers.h" +#include +#include + +namespace S2Plugin +{ + + class EntityList + { + public: + EntityList(uintptr_t _address) : address(_address){}; + uintptr_t entities() const + { + return Script::Memory::ReadQword(address); + } + + uintptr_t uids() const + { + return Script::Memory::ReadQword(address + sizeof(uintptr_t)); + } + uint32_t capacity() const + { + return Script::Memory::ReadDword(address + sizeof(uintptr_t) * 2); + } + uint32_t size() const + { + return Script::Memory::ReadDword(address + sizeof(uintptr_t) * 2 + sizeof(uint32_t)); + } + struct Iterator + { + // Iterator(){}; + Iterator(const EntityList entityList, uint32_t index) noexcept : Iterator(entityList.begin()) + { + advance(index); + } + void advance(int count) noexcept // should probably be int64 ? + { + addr.first += count * sizeof(uintptr_t); + addr.second += count * sizeof(uint32_t); + } + Iterator& operator++() noexcept + { + advance(1); + return *this; + } + Iterator operator++(int) noexcept + { + auto copy = *this; + advance(1); + return copy; + } + Iterator& operator--() noexcept + { + advance(-1); + } + Iterator operator--(int) noexcept + { + auto copy = *this; + advance(-1); + return copy; + } + std::pair operator*() const + { + return {addr.first, Script::Memory::ReadDword(addr.second)}; + } + bool operator==(const Iterator& other) const noexcept + { + return addr.first == other.addr.first; + } + bool operator!=(const Iterator& other) const noexcept + { + return addr.first != other.addr.first; + } + uintptr_t entityRaw() const + { + return Script::Memory::ReadQword(addr.first); + } + Entity entity() const + { + return Script::Memory::ReadQword(addr.first); + } + uint32_t uid() const + { + return Script::Memory::ReadDword(addr.second); + } + + private: + Iterator(uintptr_t entitiesAddress, uintptr_t uidsAddress) : addr(entitiesAddress, uidsAddress){}; + std::pair addr; + friend class EntityList; + }; + + Iterator begin() const + { + uintptr_t pointers[2] = {0, 0}; + // slightly faster then reading both thru ReadQword + Script::Memory::Read(address, &pointers, sizeof(uintptr_t) * 2, nullptr); + return {pointers[0], pointers[1]}; + } + Iterator end() const + { + auto full = getFullStruct(); + uintptr_t entitiesEnd = full.entities + full.size * sizeof(uintptr_t); + uintptr_t uidsEnd = full.uids + full.size * sizeof(uint32_t); + return {entitiesEnd, uidsEnd}; + } + const Iterator cbegin() const + { + return begin(); + } + const Iterator cend() const + { + return end(); + } + Iterator find(uint32_t uid) const + { + auto endIterator = end(); + for (auto it = begin(); it != endIterator; ++it) + { + if (it.uid() == uid) + return it; + } + return endIterator; + } + Iterator findEntity(uintptr_t addr) const + { + auto endIterator = end(); + for (auto it = begin(); it != endIterator; ++it) + { + if (it.entityRaw() == addr) + return it; + } + return endIterator; + } + + private: + uintptr_t address; + struct TrueEntityList + { + uintptr_t entities{0}; + uintptr_t uids{0}; + uint32_t cap{0}; + uint32_t size{0}; + + private: + TrueEntityList() = delete; + // TrueEntityList(const TrueEntityList&) = delete; + // TrueEntityList& operator=(const TrueEntityList&) = delete; + }; + TrueEntityList getFullStruct() const + { + return Read(address); + } + }; +}; // namespace S2Plugin diff --git a/include/Data/IDNameList.h b/include/Data/IDNameList.h index 73cd0d0..f56805e 100644 --- a/include/Data/IDNameList.h +++ b/include/Data/IDNameList.h @@ -54,12 +54,12 @@ namespace S2Plugin ParticleEmittersList(const ParticleEmittersList&) = delete; ParticleEmittersList& operator=(const ParticleEmittersList&) = delete; }; - class EntityList : public IDNameList + class EntityNamesList : public IDNameList { public: - explicit EntityList(); + explicit EntityNamesList(); - EntityList(const EntityList&) = delete; - EntityList& operator=(const EntityList&) = delete; + EntityNamesList(const EntityNamesList&) = delete; + EntityNamesList& operator=(const EntityNamesList&) = delete; }; } // namespace S2Plugin diff --git a/include/Views/ViewEntityList.h b/include/Views/ViewEntityList.h new file mode 100644 index 0000000..0617b53 --- /dev/null +++ b/include/Views/ViewEntityList.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +namespace S2Plugin +{ + class TreeViewMemoryFields; + + class ViewEntityList : public QWidget + { + Q_OBJECT + public: + ViewEntityList(uintptr_t address, QWidget* parent = nullptr); + + protected: + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + + private slots: + void refreshEntityListContents(); + + private: + uintptr_t mEntityListAddress; + + TreeViewMemoryFields* mMainTreeView; + }; +} // namespace S2Plugin diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index aa36b7e..d5a586d 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -28,6 +28,7 @@ namespace S2Plugin void showLevelGen(uintptr_t address); void showArray(uintptr_t address, std::string name, std::string arrayTypeName, size_t length); void showMatrix(uintptr_t address, std::string name, std::string arrayTypeName, size_t rows, size_t columns); + void showEntityList(uintptr_t address); public slots: ViewEntityDB* showEntityDB(); diff --git a/src/Configuration.cpp b/src/Configuration.cpp index cc7428f..53b3229 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -145,6 +145,7 @@ namespace S2Plugin {MemoryFieldType::IPv4Address, "IPv4Address", "uint32_t", "IPv4Address", 4, false}, {MemoryFieldType::Array, "Array", "", "Array", 0, false}, {MemoryFieldType::Matrix, "Matrix", "", "Matrix", 0, false}, + {MemoryFieldType::EntityList, "EntityList", "EntityList*", "EntityList", 24, false}, // Other //{MemoryFieldType::EntitySubclass, "", "", "", 0}, //{MemoryFieldType::DefaultStructType, "", "", "", 0}, diff --git a/src/Data/IDNameList.cpp b/src/Data/IDNameList.cpp index 4ac6196..a8b60f7 100644 --- a/src/Data/IDNameList.cpp +++ b/src/Data/IDNameList.cpp @@ -58,7 +58,7 @@ std::string S2Plugin::IDNameList::nameForID(uint32_t id) const static const std::regex regexEntityLine("^([0-9]+): ENT_TYPE_(.*?)$", std::regex_constants::ECMAScript); -S2Plugin::EntityList::EntityList() : IDNameList("plugins/Spelunky2Entities.txt", regexEntityLine) {} +S2Plugin::EntityNamesList::EntityNamesList() : IDNameList("plugins/Spelunky2Entities.txt", regexEntityLine) {} static const std::regex regexParticleLine("^([0-9]+): PARTICLEEMITTER_(.*?)$", std::regex_constants::ECMAScript); diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 64bf322..5ac9996 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -1880,7 +1880,12 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional value = updateField(itemField, valueMemoryOffset == 0 ? 0 : valueMemoryOffset + 0x8, itemValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); if (value.has_value()) { - itemValue->setData("Show contents", Qt::DisplayRole); + uintptr_t beginPointer = Script::Memory::ReadQword(valueMemoryOffset); + if (beginPointer == value.value()) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); + // maybe show hex as the begin pointer ? } @@ -1888,10 +1893,14 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { std::optional comparisonValue; auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x8; - comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, false, false, highlightColor); if (comparisonValue.has_value()) { - itemComparisonValue->setData("Show contents", Qt::DisplayRole); + uintptr_t beginPointer = Script::Memory::ReadQword(valueComparisonMemoryOffset); + if (beginPointer == comparisonValue.value()) + itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemComparisonValue->setData("Show contents", Qt::DisplayRole); } itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1914,7 +1923,10 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional value = updateField(itemField, valueMemoryOffset == 0 ? 0 : valueMemoryOffset + 0x8, itemValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); if (value.has_value()) { - itemValue->setData("Show contents", Qt::DisplayRole); + if (value.value() == 0) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); // maybe show hex as the pointer ? } @@ -1922,10 +1934,13 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { std::optional comparisonValue; auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x8; - comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, false, false, highlightColor); if (comparisonValue.has_value()) { - itemComparisonValue->setData("Show contents", Qt::DisplayRole); + if (comparisonValue.value() == 0) + itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemComparisonValue->setData("Show contents", Qt::DisplayRole); } // maybe it should be based on the pointer not size? itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); @@ -1941,6 +1956,44 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::EntityList: + { + std::optional value; + // we use the size to check if it was changed + value = updateField(itemField, valueMemoryOffset == 0 ? 0 : valueMemoryOffset + 0x14, itemValue, nullptr, nullptr, true, nullptr, true, !pointerUpdate, highlightColor); + if (value.has_value()) + { + if (value.value() == 0) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); + } + + if (comparisonActive) + { + std::optional comparisonValue; + auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x14; + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, false, highlightColor); + if (comparisonValue.has_value()) + { + if (comparisonValue.value() == 0) + itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemComparisonValue->setData("Show contents", Qt::DisplayRole); + } + itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + if (isPointer == false) + itemComparisonValueHex->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + } + if (shouldUpdateChildren) + { + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); + for (uint8_t x = 0; x < itemField->rowCount(); ++x) + updateRow(x, addr, comparisonAddr, itemField); + } + break; + } case MemoryFieldType::Skip: { // TODO when setting for skip is done @@ -2429,6 +2482,15 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } break; } + case MemoryFieldType::EntityList: + { + auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (offset != 0) + { + getToolbar()->showEntityList(offset); + } + break; + } } emit memoryFieldValueUpdated(index.row(), clickedItem->parent()); } diff --git a/src/Views/ViewEntityList.cpp b/src/Views/ViewEntityList.cpp new file mode 100644 index 0000000..f90b722 --- /dev/null +++ b/src/Views/ViewEntityList.cpp @@ -0,0 +1,69 @@ +#include "Views/ViewEntityList.h" + +#include "Configuration.h" +#include "Data/EntityList.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetAutorefresh.h" +#include "QtPlugin.h" +#include +#include +#include + +S2Plugin::ViewEntityList::ViewEntityList(uintptr_t address, QWidget* parent) : mEntityListAddress(address), QWidget(parent) +{ + setWindowIcon(getCavemanIcon()); + setWindowTitle("EntityList"); + + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto refreshLayout = new QHBoxLayout(); + mainLayout->addLayout(refreshLayout); + + auto refreshVectorButton = new QPushButton("Refresh list", this); + QObject::connect(refreshVectorButton, &QPushButton::clicked, this, &ViewEntityList::refreshEntityListContents); + refreshLayout->addWidget(refreshVectorButton); + + auto autoRefresh = new WidgetAutorefresh(300, this); + refreshLayout->addWidget(autoRefresh); + + mMainTreeView = new TreeViewMemoryFields(this); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, mMainTreeView, static_cast(&TreeViewMemoryFields::updateTree)); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment).disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress); + mainLayout->addWidget(mMainTreeView); + autoRefresh->toggleAutoRefresh(true); + refreshEntityListContents(); +} + +void S2Plugin::ViewEntityList::refreshEntityListContents() +{ + mMainTreeView->clear(); + + EntityList entityList{mEntityListAddress}; + + for (auto entity : entityList) + { + MemoryField entityField; + entityField.name = "uid_" + std::to_string(entity.second); + entityField.isPointer = true; + entityField.type = MemoryFieldType::EntityPointer; + mMainTreeView->addMemoryField(entityField, {}, entity.first, 0); + } + + mMainTreeView->updateTableHeader(); + mMainTreeView->setColumnWidth(gsColField, 145); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 300); + mMainTreeView->updateTree(0, 0, true); +} + +QSize S2Plugin::ViewEntityList::sizeHint() const +{ + return QSize(750, 550); +} + +QSize S2Plugin::ViewEntityList::minimumSizeHint() const +{ + return QSize(150, 150); +} diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index 9ab970e..974e75e 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -6,6 +6,7 @@ #include "Views/ViewEntities.h" #include "Views/ViewEntity.h" #include "Views/ViewEntityDB.h" +#include "Views/ViewEntityList.h" #include "Views/ViewJournalPage.h" #include "Views/ViewLevelGen.h" #include "Views/ViewLogger.h" @@ -200,6 +201,14 @@ void S2Plugin::ViewToolbar::showMatrix(uintptr_t address, std::string name, std: win->setAttribute(Qt::WA_DeleteOnClose); } +void S2Plugin::ViewToolbar::showEntityList(uintptr_t address) +{ + auto w = new ViewEntityList(address); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + // // slots: // From 824ed212a9268c9562d56a7886dbb871c34dd5d6 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 1 Jun 2024 21:37:15 +0200 Subject: [PATCH 05/20] improve performance by using the new EntityList class --- include/Data/EntityList.h | 62 ++++++++++----------- include/Views/ViewEntities.h | 4 +- src/QtHelpers/WidgetSpelunkyLevel.cpp | 13 ++--- src/Views/ViewEntities.cpp | 78 ++++++++++++--------------- src/Views/ViewEntityList.cpp | 9 ++-- 5 files changed, 77 insertions(+), 89 deletions(-) diff --git a/include/Data/EntityList.h b/include/Data/EntityList.h index 3498e3c..2ccad79 100644 --- a/include/Data/EntityList.h +++ b/include/Data/EntityList.h @@ -5,30 +5,33 @@ #include "read_helpers.h" #include #include +#include namespace S2Plugin { - + // this shoudl only ever be used as temporary class EntityList { public: - EntityList(uintptr_t _address) : address(_address){}; + EntityList(uintptr_t address) + { + Script::Memory::Read(address, this, sizeof(EntityList), nullptr); + }; uintptr_t entities() const { - return Script::Memory::ReadQword(address); + return mEntities; } - uintptr_t uids() const { - return Script::Memory::ReadQword(address + sizeof(uintptr_t)); + return mUids; } uint32_t capacity() const { - return Script::Memory::ReadDword(address + sizeof(uintptr_t) * 2); + return mCap; } uint32_t size() const { - return Script::Memory::ReadDword(address + sizeof(uintptr_t) * 2 + sizeof(uint32_t)); + return mSize; } struct Iterator { @@ -37,7 +40,7 @@ namespace S2Plugin { advance(index); } - void advance(int count) noexcept // should probably be int64 ? + void advance(int count) noexcept // should probably be int64 { addr.first += count * sizeof(uintptr_t); addr.second += count * sizeof(uint32_t); @@ -96,16 +99,12 @@ namespace S2Plugin Iterator begin() const { - uintptr_t pointers[2] = {0, 0}; - // slightly faster then reading both thru ReadQword - Script::Memory::Read(address, &pointers, sizeof(uintptr_t) * 2, nullptr); - return {pointers[0], pointers[1]}; + return {entities(), uids()}; } Iterator end() const { - auto full = getFullStruct(); - uintptr_t entitiesEnd = full.entities + full.size * sizeof(uintptr_t); - uintptr_t uidsEnd = full.uids + full.size * sizeof(uint32_t); + uintptr_t entitiesEnd = entities() + size() * sizeof(uintptr_t); + uintptr_t uidsEnd = uids() + size() * sizeof(uint32_t); return {entitiesEnd, uidsEnd}; } const Iterator cbegin() const @@ -136,24 +135,25 @@ namespace S2Plugin } return endIterator; } - - private: - uintptr_t address; - struct TrueEntityList + std::vector getAllEntities() const { - uintptr_t entities{0}; - uintptr_t uids{0}; - uint32_t cap{0}; - uint32_t size{0}; - - private: - TrueEntityList() = delete; - // TrueEntityList(const TrueEntityList&) = delete; - // TrueEntityList& operator=(const TrueEntityList&) = delete; - }; - TrueEntityList getFullStruct() const + std::vector result; + result.resize(size()); + Script::Memory::Read(entities(), result.data(), size() * sizeof(uintptr_t), nullptr); + return result; + } + std::vector getAllUids() const { - return Read(address); + std::vector result; + result.resize(size()); + Script::Memory::Read(uids(), result.data(), size() * sizeof(uint32_t), nullptr); + return result; } + + private: + uintptr_t mEntities{0}; + uintptr_t mUids{0}; + uint32_t mCap{0}; + uint32_t mSize{0}; }; }; // namespace S2Plugin diff --git a/include/Views/ViewEntities.h b/include/Views/ViewEntities.h index 4b65c05..0dd7e45 100644 --- a/include/Views/ViewEntities.h +++ b/include/Views/ViewEntities.h @@ -74,8 +74,8 @@ namespace S2Plugin QLineEdit* mFilterLineEdit; - uintptr_t mLayer0Offset = 0; - uintptr_t mLayer1Offset = 0; + uintptr_t mLayer0Address = 0; + uintptr_t mLayer1Address = 0; uintptr_t mLayerMapOffset = 0; }; } // namespace S2Plugin diff --git a/src/QtHelpers/WidgetSpelunkyLevel.cpp b/src/QtHelpers/WidgetSpelunkyLevel.cpp index dbd5529..54f1ffb 100644 --- a/src/QtHelpers/WidgetSpelunkyLevel.cpp +++ b/src/QtHelpers/WidgetSpelunkyLevel.cpp @@ -1,6 +1,7 @@ #include "QtHelpers/WidgetSpelunkyLevel.h" #include "Configuration.h" +#include "Data/EntityList.h" #include "Data/StdMap.h" #include "Spelunky2.h" #include "pluginmain.h" @@ -154,6 +155,7 @@ void S2Plugin::WidgetSpelunkyLevel::updateLevel() constexpr auto dataSize = (msLevelMaxHeight + 1) * ((msLevelMaxWidth + 1) * sizeof(uintptr_t)); Script::Memory::Read(gridAddr, &mLevelFloors, dataSize, nullptr); } + for (auto& entity : mEntitiesToPaint) entity.pos = entity.ent.abs_position(); @@ -166,14 +168,9 @@ void S2Plugin::WidgetSpelunkyLevel::updateLevel() mEntitiesMaskCoordinates[bit_number].clear(); if ((mEntityMasksToPaint & key) != 0) { - // TODO: change to proper struct when done - auto pointers = Script::Memory::ReadQword(value_ptr); - auto list_count = Script::Memory::ReadDword(value_ptr + 20); - - mEntitiesMaskCoordinates[bit_number].reserve(list_count); - std::vector entities; - entities.resize(list_count); - Script::Memory::Read(pointers, entities.data(), list_count * sizeof(uintptr_t), nullptr); + EntityList entityList{value_ptr}; + mEntitiesMaskCoordinates[bit_number].reserve(entityList.size()); + std::vector entities = entityList.getAllEntities(); for (auto entityAddr : entities) { diff --git a/src/Views/ViewEntities.cpp b/src/Views/ViewEntities.cpp index 5e01e8b..3c29f9f 100644 --- a/src/Views/ViewEntities.cpp +++ b/src/Views/ViewEntities.cpp @@ -2,6 +2,7 @@ #include "Configuration.h" #include "Data/Entity.h" +#include "Data/Entitylist.h" #include "Data/StdMap.h" #include "QtHelpers/TreeViewMemoryFields.h" #include "QtPlugin.h" @@ -20,8 +21,8 @@ S2Plugin::ViewEntities::ViewEntities(QWidget* parent) : QWidget(parent) auto mainLayout = new QVBoxLayout(this); mainLayout->setMargin(5); auto config = Configuration::get(); - mLayer0Offset = config->offsetForField(MemoryFieldType::State, "layer0", Spelunky2::get()->get_StatePtr()); - mLayer1Offset = config->offsetForField(MemoryFieldType::State, "layer1", Spelunky2::get()->get_StatePtr()); + mLayer0Address = config->offsetForField(MemoryFieldType::State, "layer0", Spelunky2::get()->get_StatePtr()); + mLayer1Address = config->offsetForField(MemoryFieldType::State, "layer1", Spelunky2::get()->get_StatePtr()); mLayerMapOffset = config->offsetForField(config->typeFieldsOfDefaultStruct("LayerPointer"), "entities_by_mask"); // initializeRefreshAndFilter @@ -90,6 +91,7 @@ void S2Plugin::ViewEntities::refreshEntities() enteredUID = mFilterLineEdit->text().toUInt(&isUIDlookupSuccess, 0); } + size_t entitiesShown = 0; auto AddEntity = [&](size_t entity_ptr) { auto entity = Entity{Script::Memory::ReadQword(entity_ptr)}; @@ -105,16 +107,16 @@ void S2Plugin::ViewEntities::refreshEntities() field.name = "entity_uid_" + std::to_string(entity.uid()); field.type = MemoryFieldType::EntityPointer; field.isPointer = true; - mMainTreeView->addMemoryField(field, "", entity_ptr, 0); + mMainTreeView->addMemoryField(field, {}, entity_ptr, 0); + ++entitiesShown; }; - size_t totalEntities = 0; - auto layer0 = Script::Memory::ReadQword(mLayer0Offset); - auto layer0Count = Script::Memory::ReadDword(layer0 + 0x1C); - auto layer1 = Script::Memory::ReadQword(mLayer1Offset); - auto layer1Count = Script::Memory::ReadDword(layer1 + 0x1C); - mCheckboxLayer0->setText(QString("Front layer (%1)").arg(layer0Count)); - mCheckboxLayer1->setText(QString("Back layer (%1)").arg(layer1Count)); + auto layer0 = Script::Memory::ReadQword(mLayer0Address); + EntityList entListLayer0{layer0 + 0x8}; + auto layer1 = Script::Memory::ReadQword(mLayer1Address); + EntityList entListLayer1{layer1 + 0x8}; + mCheckboxLayer0->setText(QString("Front layer (%1)").arg(entListLayer0.size())); + mCheckboxLayer1->setText(QString("Back layer (%1)").arg(entListLayer1.size())); auto check_layer0 = mCheckboxLayer0->checkState() == Qt::Checked; auto check_layer1 = mCheckboxLayer1->checkState() == Qt::Checked; @@ -122,30 +124,26 @@ void S2Plugin::ViewEntities::refreshEntities() if (isUIDlookupSuccess) { // loop thru all entities to find the uid - // TODO: change to proper struct when done - auto ent_list = Script::Memory::ReadQword(layer0 + 0x8); - auto uid_list = Script::Memory::ReadQword(layer0 + 0x10); + auto uidList0 = entListLayer0.getAllUids(); bool found_uid = false; - for (uint idx = 0; idx < layer0Count; ++idx) + for (uint idx = 0; idx < entListLayer0.size(); ++idx) { - auto uid = Script::Memory::ReadDword(uid_list + idx * sizeof(uint32_t)); - if (enteredUID == uid) + if (enteredUID == uidList0[idx]) { - AddEntity(ent_list + idx * sizeof(size_t)); + AddEntity(entListLayer0.entities() + idx * sizeof(size_t)); found_uid = true; break; } } - ent_list = Script::Memory::ReadQword(layer1 + 0x8); - uid_list = Script::Memory::ReadQword(layer1 + 0x10); + if (found_uid == false) { - for (uint idx = 0; idx < layer1Count; ++idx) + auto uidList1 = entListLayer1.getAllUids(); + for (uint idx = 0; idx < entListLayer1.size(); ++idx) { - auto uid = Script::Memory::ReadDword(uid_list + idx * sizeof(uint32_t)); - if (enteredUID == uid) + if (enteredUID == uidList1[idx]) { - AddEntity(ent_list + idx * sizeof(size_t)); + AddEntity(entListLayer1.entities() + idx * sizeof(size_t)); break; } } @@ -164,19 +162,14 @@ void S2Plugin::ViewEntities::refreshEntities() auto itr = map0.find(checkbox.mask); if (itr != map0.end()) { - // TODO: change to proper struct when done - auto ent_list = itr.value_ptr(); - auto pointers = Script::Memory::ReadQword(ent_list); - auto list_count = Script::Memory::ReadDword(ent_list + 20); - field_count += list_count; + EntityList maskEntList{itr.value_ptr()}; + + field_count += maskEntList.size(); // loop only if uid was not entered and the mask was choosen - if (!isUIDlookupSuccess && totalEntities < 10000u && checkbox.mCheckbox->checkState() == Qt::Checked) + if (!isUIDlookupSuccess && checkbox.mCheckbox->checkState() == Qt::Checked) { - for (size_t i = 0; i < list_count; ++i) - { - AddEntity(pointers + (i * sizeof(uintptr_t))); - ++totalEntities; - } + for (size_t i = 0; i < maskEntList.size(); ++i) + AddEntity(maskEntList.entities() + i * sizeof(uintptr_t)); } } } @@ -185,23 +178,18 @@ void S2Plugin::ViewEntities::refreshEntities() auto itr = map1.find(checkbox.mask); if (itr != map1.end()) { - auto ent_list = itr.value_ptr(); - auto pointers = Script::Memory::ReadQword(ent_list); - auto list_count = Script::Memory::ReadDword(ent_list + 20); - field_count += list_count; - if (!isUIDlookupSuccess && totalEntities < 10000u && checkbox.mCheckbox->checkState() == Qt::Checked) + EntityList maskEntList{itr.value_ptr()}; + field_count += maskEntList.size(); + if (!isUIDlookupSuccess && checkbox.mCheckbox->checkState() == Qt::Checked) { - for (size_t i = 0; i < list_count; ++i) - { - AddEntity(pointers + (i * sizeof(size_t))); - ++totalEntities; - } + for (size_t i = 0; i < maskEntList.size(); ++i) + AddEntity(maskEntList.entities() + i * sizeof(uintptr_t)); } } } checkbox.mCheckbox->setText(QString(checkbox.name + " (%1)").arg(field_count)); } - setWindowTitle(QString("%1 Entities").arg(totalEntities)); + setWindowTitle(QString("%1 Entities").arg(entitiesShown)); mMainTreeView->updateTableHeader(); mMainTreeView->setColumnWidth(gsColField, 145); mMainTreeView->setColumnWidth(gsColValueHex, 125); diff --git a/src/Views/ViewEntityList.cpp b/src/Views/ViewEntityList.cpp index f90b722..29d4bd3 100644 --- a/src/Views/ViewEntityList.cpp +++ b/src/Views/ViewEntityList.cpp @@ -40,13 +40,16 @@ void S2Plugin::ViewEntityList::refreshEntityListContents() EntityList entityList{mEntityListAddress}; - for (auto entity : entityList) + // TODO using custom view instead of memory view would improve performance + auto entities = entityList.entities(); + auto uids = entityList.getAllUids(); + for (size_t idx = 0; idx < entityList.size(); ++idx) { MemoryField entityField; - entityField.name = "uid_" + std::to_string(entity.second); + entityField.name = "uid_" + std::to_string(uids[idx]); entityField.isPointer = true; entityField.type = MemoryFieldType::EntityPointer; - mMainTreeView->addMemoryField(entityField, {}, entity.first, 0); + mMainTreeView->addMemoryField(entityField, {}, entities + idx * sizeof(uintptr_t), 0); } mMainTreeView->updateTableHeader(); From bff3f4b9b0c885c818f78962255e148dde94ca0b Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 2 Jun 2024 14:43:22 +0200 Subject: [PATCH 06/20] Rename `WidgetDatabaseView` to `AbstractDatabaseView`, also `vectortype` to `valuetype` in json, (WIP) add old implementation of std::list --- CMakeLists.txt | 5 +- include/Configuration.h | 1 + include/Data/OldStdList.h | 101 +++++++++++++++ ...tDatabaseView.h => AbstractDatabaseView.h} | 4 +- include/Views/ViewCharacterDB.h | 4 +- include/Views/ViewEntityDB.h | 4 +- include/Views/ViewParticleDB.h | 4 +- include/Views/ViewTextureDB.h | 4 +- resources/Spelunky2.json | 67 +++++----- src/Configuration.cpp | 22 +++- ...abaseView.cpp => AbstractDatabaseView.cpp} | 48 +++---- src/QtHelpers/TreeViewMemoryFields.cpp | 121 +++++++++++++++--- src/Views/ViewCharacterDB.cpp | 2 +- src/Views/ViewEntityDB.cpp | 2 +- src/Views/ViewParticleDB.cpp | 2 +- src/Views/ViewTextureDB.cpp | 2 +- 16 files changed, 300 insertions(+), 93 deletions(-) create mode 100644 include/Data/OldStdList.h rename include/QtHelpers/{WidgetDatabaseView.h => AbstractDatabaseView.h} (95%) rename src/QtHelpers/{WidgetDatabaseView.cpp => AbstractDatabaseView.cpp} (90%) diff --git a/CMakeLists.txt b/CMakeLists.txt index d369cd3..4bea818 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,6 +61,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Data/StdString.h include/Data/StdMap.h include/Data/EntityList.h + include/Data/OldStdList.h include/Views/ViewToolbar.h include/Views/ViewEntityDB.h include/Views/ViewParticleDB.h @@ -101,7 +102,7 @@ x64dbg_plugin(${PROJECT_NAME} include/QtHelpers/WidgetSampling.h include/QtHelpers/WidgetSamplesPlot.h include/QtHelpers/ItemModelLoggerSamples.h - include/QtHelpers/WidgetDatabaseView.h + include/QtHelpers/AbstractDatabaseView.h include/QtHelpers/WidgetAutorefresh.h include/QtHelpers/LongLongSpinBox.h src/Spelunky2.cpp @@ -151,7 +152,7 @@ x64dbg_plugin(${PROJECT_NAME} src/QtHelpers/ItemModelLoggerFields.cpp src/QtHelpers/WidgetSamplesPlot.cpp src/QtHelpers/ItemModelLoggerSamples.cpp - src/QtHelpers/WidgetDatabaseView.cpp + src/QtHelpers/AbstractDatabaseView.cpp src/QtHelpers/WidgetAutorefresh.cpp ${CMAKE_CURRENT_BINARY_DIR}/include/pluginconfig.h resources/spelunky2.qrc diff --git a/include/Configuration.h b/include/Configuration.h index 31e9443..be2e17d 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -130,6 +130,7 @@ namespace S2Plugin Array, Matrix, EntityList, + OldStdList, }; struct VirtualFunction diff --git a/include/Data/OldStdList.h b/include/Data/OldStdList.h new file mode 100644 index 0000000..2bd2a84 --- /dev/null +++ b/include/Data/OldStdList.h @@ -0,0 +1,101 @@ +#pragma once + +#include "pluginmain.h" +#include + +namespace S2Plugin +{ + // this is the pre C++ 11 version standard + class OldStdList + { + public: + OldStdList(uintptr_t address) : _end(address){}; + + struct Node + { + Node(uintptr_t address) : nodeAddress(address){}; + + Node prev() const + { + return Script::Memory::ReadQword(nodeAddress); + } + Node next() const + { + return Script::Memory::ReadQword(nodeAddress + sizeof(uintptr_t)); + } + uintptr_t value_ptr() const + { + return nodeAddress + 2 * sizeof(uintptr_t); + } + uintptr_t operator*() const + { + return value_ptr(); + } + Node operator++() + { + nodeAddress = next().nodeAddress; + return *this; + } + Node operator--(int) + { + auto tmp = *this; + nodeAddress = prev().nodeAddress; + return tmp; + } + Node operator++(int) + { + auto tmp = *this; + nodeAddress = next().nodeAddress; + return tmp; + } + Node operator--() + { + nodeAddress = prev().nodeAddress; + return *this; + } + bool operator==(const Node& other) const + { + return nodeAddress == other.nodeAddress; + } + bool operator!=(const Node& other) const + { + return nodeAddress != other.nodeAddress; + } + + private: + uintptr_t nodeAddress; + }; + + Node begin() const + { + return _end.next(); + } + Node end() const + { + return _end; + } + bool empty() const + { + return begin() == end(); + } + Node cbegin() const + { + return begin(); + } + Node cend() const + { + return end(); + } + Node back() const + { + return _end.prev(); + } + Node front() const + { + return _end.next(); + } + + private: + Node _end; + }; +}; // namespace S2Plugin diff --git a/include/QtHelpers/WidgetDatabaseView.h b/include/QtHelpers/AbstractDatabaseView.h similarity index 95% rename from include/QtHelpers/WidgetDatabaseView.h rename to include/QtHelpers/AbstractDatabaseView.h index 36dadae..6eb462e 100644 --- a/include/QtHelpers/WidgetDatabaseView.h +++ b/include/QtHelpers/AbstractDatabaseView.h @@ -33,11 +33,11 @@ namespace S2Plugin struct MemoryField; using ID_type = uint32_t; - class WidgetDatabaseView : public QWidget + class AbstractDatabaseView : public QWidget { Q_OBJECT public: - WidgetDatabaseView(MemoryFieldType type, QWidget* parent = nullptr); + AbstractDatabaseView(MemoryFieldType type, QWidget* parent = nullptr); virtual void showID(ID_type id) = 0; protected: diff --git a/include/Views/ViewCharacterDB.h b/include/Views/ViewCharacterDB.h index afc8623..1fc0d99 100644 --- a/include/Views/ViewCharacterDB.h +++ b/include/Views/ViewCharacterDB.h @@ -1,12 +1,12 @@ #pragma once -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewCharacterDB : public WidgetDatabaseView + class ViewCharacterDB : public AbstractDatabaseView { Q_OBJECT public: diff --git a/include/Views/ViewEntityDB.h b/include/Views/ViewEntityDB.h index 6ddea74..8274e69 100644 --- a/include/Views/ViewEntityDB.h +++ b/include/Views/ViewEntityDB.h @@ -1,12 +1,12 @@ #pragma once -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewEntityDB : public WidgetDatabaseView + class ViewEntityDB : public AbstractDatabaseView { Q_OBJECT public: diff --git a/include/Views/ViewParticleDB.h b/include/Views/ViewParticleDB.h index 5dbc25e..dd988d9 100644 --- a/include/Views/ViewParticleDB.h +++ b/include/Views/ViewParticleDB.h @@ -1,12 +1,12 @@ #pragma once -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewParticleDB : public WidgetDatabaseView + class ViewParticleDB : public AbstractDatabaseView { Q_OBJECT public: diff --git a/include/Views/ViewTextureDB.h b/include/Views/ViewTextureDB.h index 8a03de1..8ed98bb 100644 --- a/include/Views/ViewTextureDB.h +++ b/include/Views/ViewTextureDB.h @@ -1,12 +1,12 @@ #pragma once -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" namespace S2Plugin { class TreeViewMemoryFields; - class ViewTextureDB : public WidgetDatabaseView + class ViewTextureDB : public AbstractDatabaseView { Q_OBJECT public: diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 09bfb98..16d6a01 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -1,6 +1,6 @@ { // list of structs that are always used as pointers - // leftover of previous version but also required if type is used as secondary type (vectortype, map type etc.) + // leftover of previous version but also required if type is used as secondary type (vector, map, array etc.) // normal pointers can be defined at the specific field level with ("pointer": true) // structs not listed here and not defined with pointer value will be treated as inline sturct "pointer_types": [ @@ -1398,7 +1398,7 @@ { "field": "unknown11", "type": "StdVector", - "vectortype": "DataPointer" + "valuetype": "DataPointer" }, { "field": "unknown12", "type": "DataPointer" }, { "field": "unknown13", "type": "CodePointer" }, @@ -1467,12 +1467,12 @@ { "field": "pages", "type": "StdVector", - "vectortype": "JournalPagePointer" + "valuetype": "JournalPagePointer" }, { "field": "pages_tmp", "type": "StdVector", - "vectortype": "JournalPagePointer", + "valuetype": "JournalPagePointer", "comment": "pages are constructed, saved here and later moved to the pages vector from where they are rendered" }, { "field": "current_page", "type": "UnsignedDword" }, @@ -1637,7 +1637,7 @@ { "field": "text_lines", "type": "StdVector", - "vectortype": "TextRenderingInfoPointer" + "valuetype": "TextRenderingInfoPointer" }, { "field": "x", "type": "Float" }, { "field": "y", "type": "Float" } @@ -1917,7 +1917,7 @@ { "field": "vector", "type": "StdVector", - "vectortype": "ScreenMenuOption" + "valuetype": "ScreenMenuOption" } ], "ScreenMenuOption": [ @@ -1996,12 +1996,12 @@ { "field": "menu_tree", "type": "StdVector", - "vectortype": "ScreenMenuVector" + "valuetype": "ScreenMenuVector" }, { "field": "menu_index_order", "type": "StdVector", - "vectortype": "UnsignedDword" + "valuetype": "UnsignedDword" }, { "field": "controls", "type": "ScreenControls" }, { "field": "selected_menu_index", "type": "UnsignedDword" }, @@ -2045,12 +2045,12 @@ { "field": "menu_tree", "type": "StdVector", - "vectortype": "ScreenMenuVector" + "valuetype": "ScreenMenuVector" }, { "field": "menu_index_order", "type": "StdVector", - "vectortype": "UnsignedDword" + "valuetype": "UnsignedDword" }, { "field": "DOWN", @@ -2269,7 +2269,7 @@ { "field": "unknown50", "type": "StdVector", - "vectortype": "UnsignedQword" + "valuetype": "UnsignedQword" }, { "field": "unknown51", "type": "UnsignedByte" }, { "field": "padding_probably10", "type": "UnsignedByte" }, @@ -2278,7 +2278,7 @@ { "field": "tooltip_text", "type": "StdVector", - "vectortype": "StringsTableID" + "valuetype": "StringsTableID" }, { "field": "disable_controls", "type": "Bool" }, { "field": "padding_probably13", "type": "UnsignedByte" }, @@ -2771,7 +2771,7 @@ { "field": "option_captions", "type": "StdVector", - "vectortype": "ArenaRulesStringsEntry" + "valuetype": "ArenaRulesStringsEntry" }, { "field": "unknown48", "type": "Bool" }, { "field": "unknown49", "type": "UnsignedByte" }, @@ -3352,7 +3352,7 @@ { "field": "entities_switching_layer", "type": "StdVector", - "vectortype": "BackLayerRelated" + "valuetype": "BackLayerRelated" }, { "field": "layer_transition_timer", "type": "UnsignedDword" }, { "field": "transition_to_layer", "type": "UnsignedByte" }, @@ -3605,7 +3605,7 @@ { "field": "shop_owners", "type": "StdVector", - "vectortype": "ShopOwnerDetails" + "valuetype": "ShopOwnerDetails" } ], "ItemOwnerDetails": [ @@ -4125,7 +4125,7 @@ "field": "activefloors", "type": "PointerToPushBlocksMap" }, - { "field": "impostors", "type": "StdVector", "vectortype": "LiquidLake" }, + { "field": "impostors", "type": "StdVector", "valuetype": "LiquidLake" }, { "field": "total_liquid_spawned", "type": "UnsignedDword", @@ -4317,7 +4317,7 @@ { "field": "unknown13", "type": "StdVector", - "vectortype": "PointerToEntityList", + "valuetype": "PointerToEntityList", "comment": "probably not actual vector" } ], @@ -4462,7 +4462,7 @@ { "field": "torches", "type": "StdVector", - "vectortype": "EntityPointer" + "valuetype": "EntityPointer" }, { "field": "start_countdown", "type": "UnsignedByte" }, { "field": "unknown9", "type": "UnsignedByte" }, @@ -4502,7 +4502,7 @@ { "field": "magman_spawns", "type": "StdVector", - "vectortype": "MagmamanSpawnData" + "valuetype": "MagmamanSpawnData" } ], "LogicUnderwaterBubbles": [ @@ -6299,7 +6299,7 @@ { "field": "exit_doors_locations", "type": "StdVector", - "vectortype": "XY" + "valuetype": "XY" }, { "field": "flags", @@ -6546,17 +6546,17 @@ { "field": "unknown47", "type": "StdVector", - "vectortype": "UnsignedQword" + "valuetype": "UnsignedQword" }, { "field": "unknown48", "type": "StdVector", - "vectortype": "UnsignedQword" + "valuetype": "UnsignedQword" }, { "field": "unknown49", "type": "StdVector", - "vectortype": "UnsignedQword" + "valuetype": "UnsignedQword" } ], "OnlineServer": [ @@ -6803,7 +6803,7 @@ { "field": "_Vec", "type": "StdVector", - "vectortype": "UnorderedMapBucketIterator", + "valuetype": "UnorderedMapBucketIterator", "comment": "iterators for buckets, if both are pointing to end, the bucket is empty" }, { @@ -6888,14 +6888,14 @@ { "field": "vector", "type": "StdVector", - "vectortype": "ParticleEmitterInfoPointer" + "valuetype": "ParticleEmitterInfoPointer" } ], "StdVectorPointerIllumination": [ { "field": "vector", "type": "StdVector", - "vectortype": "IlluminationPointer" + "valuetype": "IlluminationPointer" }, { "field": "unknown1", "type": "UnsignedDword" }, { "field": "unknown2", "type": "UnsignedDword" } @@ -7020,7 +7020,7 @@ { "field": "unknown3", "type": "StdVector", - "vectortype": "EntityPointer" + "valuetype": "EntityPointer" }, { "field": "unknown6", "type": "PointerToFloatSet" }, { "field": "expired_entities", "type": "EntityList" }, @@ -7786,7 +7786,7 @@ { "field": "unknown2", "type": "Dword", "comment": "padding probably" }, { "field": "sound_name", "type": "StdString" } ], - "StdList": [ + "OldStdList": [ { "field": "back", "type": "StdListIteratorPointer" }, { "field": "front", "type": "StdListIteratorPointer" } ], @@ -7831,7 +7831,7 @@ "type": "UnsignedDword", "comment": "padding probably" }, - { "field": "unk1", "type": "StdList" }, + { "field": "unk1", "type": "OldStdList" }, { "field": "resize_value", "type": "UnsignedDword", @@ -7842,10 +7842,10 @@ "type": "UnsignedDword", "comment": "padding probably" }, - { "field": "liquid_ids", "type": "StdList" }, + { "field": "liquid_ids", "type": "OldStdList" }, { "field": "unknown44", - "type": "StdList", + "type": "OldStdList", "comment": "all of them are -1" }, { @@ -8078,7 +8078,10 @@ "comment": "array of entities? removed ones?" }, { "field": "unknown10", "type": "DataPointer", "comment": "map?" }, - { "field": "unknown11", "type": "Qword" } + { "field": "unknown11", "type": "Qword" }, + { "field": "skip", "type": "Skip", "offset": 102472 }, + { "field": "unknown12", "type": "Array", "length": 46, "arraytype": "UnsignedQword" }, + { "field": "unknown13", "type": "Qword", "comment": "the end?" } ], "MysteryLiquidPointer2": [ { "field": "last_spawn_liquid", "type": "EntityPointer" }, diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 53b3229..bc6ee50 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -112,6 +112,7 @@ namespace S2Plugin {MemoryFieldType::StdMap, "StdMap", "std::map", "StdMap", 16, false}, {MemoryFieldType::StdString, "StdString", "std::string", "StdString", 32, false}, {MemoryFieldType::StdWstring, "StdWstring", "std::wstring", "StdWstring", 32, false}, + {MemoryFieldType::OldStdList, "OldStdList", "std::pair", "OldStdList", 16, false}, // can't use std::list representation since the standard was changed // Game Main structs {MemoryFieldType::GameManager, "GameManager", "", "GameManager", 0, false}, {MemoryFieldType::State, "State", "", "State", 0, false}, @@ -331,14 +332,27 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman } case MemoryFieldType::StdVector: { - if (field.contains("vectortype")) + if (field.contains("valuetype")) { - memField.firstParameterType = field["vectortype"].get(); + memField.firstParameterType = field["valuetype"].get(); } else { memField.firstParameterType = "UnsignedQword"; - dprintf("no vectortype specified for StdVector (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); + dprintf("no valuetype specified for StdVector (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); + } + break; + } + case MemoryFieldType::OldStdList: + { + if (field.contains("valuetype")) + { + memField.firstParameterType = field["valuetype"].get(); + } + else + { + memField.firstParameterType = "UnsignedQword"; + dprintf("no valuetype specified for OldStdList (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); } break; } @@ -810,6 +824,8 @@ int S2Plugin::Configuration::getAlingment(const std::string& typeName) const case MemoryFieldType::Qword: case MemoryFieldType::UnsignedQword: case MemoryFieldType::Double: + case MemoryFieldType::OldStdList: + case MemoryFieldType::EntityList: return sizeof(uintptr_t); } } diff --git a/src/QtHelpers/WidgetDatabaseView.cpp b/src/QtHelpers/AbstractDatabaseView.cpp similarity index 90% rename from src/QtHelpers/WidgetDatabaseView.cpp rename to src/QtHelpers/AbstractDatabaseView.cpp index 34ffdad..46bce7c 100644 --- a/src/QtHelpers/WidgetDatabaseView.cpp +++ b/src/QtHelpers/AbstractDatabaseView.cpp @@ -1,4 +1,4 @@ -#include "QtHelpers/WidgetDatabaseView.h" +#include "QtHelpers/AbstractDatabaseView.h" #include "Configuration.h" #include "QtHelpers/StyledItemDelegateHTML.h" @@ -37,7 +37,7 @@ struct ComparisonField }; Q_DECLARE_METATYPE(ComparisonField) -S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* parent) : QWidget(parent) +S2Plugin::AbstractDatabaseView::AbstractDatabaseView(MemoryFieldType type, QWidget* parent) : QWidget(parent) { setWindowIcon(getCavemanIcon()); auto mainLayout = new QVBoxLayout(this); @@ -68,10 +68,10 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mSearchLineEdit = new QLineEdit(); mSearchLineEdit->setPlaceholderText("Search"); topLayout->addWidget(mSearchLineEdit); - QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &WidgetDatabaseView::searchFieldReturnPressed); + QObject::connect(mSearchLineEdit, &QLineEdit::returnPressed, this, &AbstractDatabaseView::searchFieldReturnPressed); auto labelButton = new QPushButton("Label", this); - QObject::connect(labelButton, &QPushButton::clicked, this, &WidgetDatabaseView::label); + QObject::connect(labelButton, &QPushButton::clicked, this, &AbstractDatabaseView::label); topLayout->addWidget(labelButton); qobject_cast(tabLookup->layout())->addLayout(topLayout); @@ -79,8 +79,8 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mMainTreeView = new TreeViewMemoryFields(this); mMainTreeView->setEnableChangeHighlighting(false); mMainTreeView->addMemoryFields(config->typeFields(type), std::string(config->getTypeDisplayName(type)), 0); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &WidgetDatabaseView::fieldUpdated); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &WidgetDatabaseView::fieldExpanded); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &AbstractDatabaseView::fieldUpdated); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &AbstractDatabaseView::fieldExpanded); tabLookup->layout()->addWidget(mMainTreeView); mMainTreeView->setColumnWidth(gsColField, 125); mMainTreeView->setColumnWidth(gsColValue, 250); @@ -99,16 +99,16 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mCompareFieldComboBox->addItem(QString::fromStdString("")); populateComparisonCombobox(config->typeFields(type)); - QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &WidgetDatabaseView::comparisonFieldChosen); + QObject::connect(mCompareFieldComboBox, &QComboBox::currentTextChanged, this, &AbstractDatabaseView::comparisonFieldChosen); topLayout->addWidget(mCompareFieldComboBox); mCompareFlagComboBox = new QComboBox(); - QObject::connect(mCompareFlagComboBox, &QComboBox::currentTextChanged, this, &WidgetDatabaseView::comparisonFlagChosen); + QObject::connect(mCompareFlagComboBox, &QComboBox::currentTextChanged, this, &AbstractDatabaseView::comparisonFlagChosen); mCompareFlagComboBox->hide(); topLayout->addWidget(mCompareFlagComboBox); auto groupCheckbox = new QCheckBox("Group by value", this); - QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &WidgetDatabaseView::compareGroupByCheckBoxClicked); + QObject::connect(groupCheckbox, &QCheckBox::stateChanged, this, &AbstractDatabaseView::compareGroupByCheckBoxClicked); topLayout->addWidget(groupCheckbox); qobject_cast(tabCompare->layout())->addLayout(topLayout); @@ -128,14 +128,14 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mCompareTableWidget->setColumnWidth(2, 150); auto HTMLDelegate = new StyledItemDelegateHTML(this); mCompareTableWidget->setItemDelegate(HTMLDelegate); - QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &WidgetDatabaseView::comparisonCellClicked); + QObject::connect(mCompareTableWidget, &QTableWidget::cellClicked, this, &AbstractDatabaseView::comparisonCellClicked); mCompareTreeWidget = new QTreeWidget(this); mCompareTreeWidget->setAlternatingRowColors(true); mCompareTreeWidget->headerItem()->setHidden(true); mCompareTreeWidget->setHidden(true); mCompareTreeWidget->setItemDelegate(HTMLDelegate); - QObject::connect(mCompareTreeWidget, &QTreeWidget::itemClicked, this, &WidgetDatabaseView::groupedComparisonItemClicked); + QObject::connect(mCompareTreeWidget, &QTreeWidget::itemClicked, this, &AbstractDatabaseView::groupedComparisonItemClicked); tabCompare->layout()->addWidget(mCompareTableWidget); tabCompare->layout()->addWidget(mCompareTreeWidget); @@ -144,12 +144,12 @@ S2Plugin::WidgetDatabaseView::WidgetDatabaseView(MemoryFieldType type, QWidget* mSearchLineEdit->setFocus(); } -QSize S2Plugin::WidgetDatabaseView::minimumSizeHint() const +QSize S2Plugin::AbstractDatabaseView::minimumSizeHint() const { return QSize(150, 150); } -void S2Plugin::WidgetDatabaseView::searchFieldReturnPressed() +void S2Plugin::AbstractDatabaseView::searchFieldReturnPressed() { auto text = mSearchLineEdit->text(); bool isNumeric = false; @@ -169,7 +169,7 @@ void S2Plugin::WidgetDatabaseView::searchFieldReturnPressed() } } -void S2Plugin::WidgetDatabaseView::fieldUpdated(int row, QStandardItem* parrent) +void S2Plugin::AbstractDatabaseView::fieldUpdated(int row, QStandardItem* parrent) { if (parrent != nullptr) // special case: for flag field need to update it's parrent, not the flag field { @@ -185,19 +185,19 @@ void S2Plugin::WidgetDatabaseView::fieldUpdated(int row, QStandardItem* parrent) mMainTreeView->updateRow(row, std::nullopt, std::nullopt, parrent, true); } -void S2Plugin::WidgetDatabaseView::fieldExpanded(const QModelIndex& index) +void S2Plugin::AbstractDatabaseView::fieldExpanded(const QModelIndex& index) { auto model = qobject_cast(mMainTreeView->model()); mMainTreeView->updateRow(index.row(), std::nullopt, std::nullopt, model->itemFromIndex(index.parent()), true); } -void S2Plugin::WidgetDatabaseView::compareGroupByCheckBoxClicked(int state) +void S2Plugin::AbstractDatabaseView::compareGroupByCheckBoxClicked(int state) { mCompareTableWidget->setHidden(state == Qt::Checked); mCompareTreeWidget->setHidden(state == Qt::Unchecked); } -void S2Plugin::WidgetDatabaseView::comparisonFieldChosen() +void S2Plugin::AbstractDatabaseView::comparisonFieldChosen() { mFieldChoosen = true; mCompareTableWidget->clearContents(); @@ -245,7 +245,7 @@ void S2Plugin::WidgetDatabaseView::comparisonFieldChosen() mFieldChoosen = false; } -void S2Plugin::WidgetDatabaseView::comparisonFlagChosen(const QString& text) +void S2Plugin::AbstractDatabaseView::comparisonFlagChosen(const QString& text) { // protect againts infinite loops since this slot is called when adding firts element to combo box if (mFieldChoosen) @@ -282,7 +282,7 @@ void S2Plugin::WidgetDatabaseView::comparisonFlagChosen(const QString& text) populateComparisonTreeWidget(newData); } -void S2Plugin::WidgetDatabaseView::populateComparisonTableWidget(const QVariant& fieldData) +void S2Plugin::AbstractDatabaseView::populateComparisonTableWidget(const QVariant& fieldData) { mCompareTableWidget->setSortingEnabled(false); @@ -309,7 +309,7 @@ void S2Plugin::WidgetDatabaseView::populateComparisonTableWidget(const QVariant& mCompareTableWidget->sortItems(0); } -void S2Plugin::WidgetDatabaseView::populateComparisonTreeWidget(const QVariant& fieldData) +void S2Plugin::AbstractDatabaseView::populateComparisonTreeWidget(const QVariant& fieldData) { mCompareTreeWidget->setSortingEnabled(false); @@ -352,7 +352,7 @@ void S2Plugin::WidgetDatabaseView::populateComparisonTreeWidget(const QVariant& mCompareTreeWidget->sortItems(0, Qt::AscendingOrder); } -void S2Plugin::WidgetDatabaseView::comparisonCellClicked(int row, int column) +void S2Plugin::AbstractDatabaseView::comparisonCellClicked(int row, int column) { if (column == 1) { @@ -362,7 +362,7 @@ void S2Plugin::WidgetDatabaseView::comparisonCellClicked(int row, int column) } } -void S2Plugin::WidgetDatabaseView::groupedComparisonItemClicked(QTreeWidgetItem* item) +void S2Plugin::AbstractDatabaseView::groupedComparisonItemClicked(QTreeWidgetItem* item) { if (item->childCount() == 0) { @@ -371,7 +371,7 @@ void S2Plugin::WidgetDatabaseView::groupedComparisonItemClicked(QTreeWidgetItem* } } -size_t S2Plugin::WidgetDatabaseView::populateComparisonCombobox(const std::vector& fields, size_t offset, std::string prefix) +size_t S2Plugin::AbstractDatabaseView::populateComparisonCombobox(const std::vector& fields, size_t offset, std::string prefix) { for (const auto& field : fields) { @@ -416,7 +416,7 @@ size_t S2Plugin::WidgetDatabaseView::populateComparisonCombobox(const std::vecto return offset; } -std::pair S2Plugin::WidgetDatabaseView::valueForField(const QVariant& data, uintptr_t addr) +std::pair S2Plugin::AbstractDatabaseView::valueForField(const QVariant& data, uintptr_t addr) { ComparisonField compData = qvariant_cast(data); diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 5ac9996..b5e0c64 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -4,6 +4,7 @@ #include "Data/CharacterDB.h" #include "Data/Entity.h" #include "Data/EntityDB.h" +#include "Data/OldStdList.h" #include "Data/ParticleDB.h" #include "Data/State.h" #include "Data/StdString.h" @@ -1956,6 +1957,80 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::OldStdList: + { + std::optional> value; + if (valueMemoryOffset == 0) + { + itemValue->setData({}, Qt::DisplayRole); + if (!isPointer) + itemValueHex->setData({}, Qt::DisplayRole); + + itemValue->setData({}, S2Plugin::gsRoleRawValue); + itemField->setBackground(Qt::transparent); + } + else + { + value = {0, 0}; + Script::Memory::Read(valueMemoryOffset, &value.value(), 2 * sizeof(uintptr_t), nullptr); + auto dataOld = itemValue->data(S2Plugin::gsRoleRawValue); + auto valueOld = dataOld.value>(); + if (!dataOld.isValid() || value.value() != valueOld) + { + itemField->setBackground(highlightColor); + itemValue->setData(QVariant::fromValue(value.value()), S2Plugin::gsRoleRawValue); + + OldStdList list{valueMemoryOffset}; + if (list.empty()) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); + } + else if (!isPointer) + itemField->setBackground(Qt::transparent); + } + + if (comparisonActive) + { + std::optional> comparisonValue; + if (valueComparisonMemoryOffset == 0) + { + itemComparisonValue->setData({}, Qt::DisplayRole); + if (!isPointer) + itemComparisonValueHex->setData({}, Qt::DisplayRole); + + itemComparisonValue->setData({}, S2Plugin::gsRoleRawValue); + } + else + { + comparisonValue = {0, 0}; + Script::Memory::Read(valueComparisonMemoryOffset, &comparisonValue.value(), 2 * sizeof(uintptr_t), nullptr); + auto dataOld = itemComparisonValue->data(S2Plugin::gsRoleRawValue); + auto valueOld = dataOld.value>(); + if (!dataOld.isValid() || comparisonValue.value() != valueOld) + { + itemComparisonValue->setData(QVariant::fromValue(comparisonValue.value()), S2Plugin::gsRoleRawValue); + + OldStdList list{valueComparisonMemoryOffset}; + if (list.empty()) + itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemComparisonValue->setData("Show contents", Qt::DisplayRole); + } + } + itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + if (isPointer == false) + itemComparisonValueHex->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + } + if (shouldUpdateChildren) + { + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); + for (uint8_t x = 0; x < itemField->rowCount(); ++x) + updateRow(x, addr, comparisonAddr, itemField); + } + break; + } case MemoryFieldType::EntityList: { std::optional value; @@ -2429,65 +2504,75 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } case MemoryFieldType::UTF16Char: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) { auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); QChar c = static_cast(clickedItem->data(gsRoleRawValue).toUInt()); - auto dialog = new DialogEditString(fieldName, c, offset, 1, dataType, this); + auto dialog = new DialogEditString(fieldName, c, address, 1, dataType, this); dialog->exec(); } break; } case MemoryFieldType::UTF16StringFixedSize: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) { int size = getDataFrom(index, gsColField, gsRoleSize).toInt(); auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); auto stringData = std::make_unique(size); - Script::Memory::Read(offset, stringData.get(), size, nullptr); + Script::Memory::Read(address, stringData.get(), size, nullptr); auto s = QString::fromUtf16(stringData.get()); - auto dialog = new DialogEditString(fieldName, s, offset, size / 2 - 1, dataType, this); + auto dialog = new DialogEditString(fieldName, s, address, size / 2 - 1, dataType, this); dialog->exec(); } break; } case MemoryFieldType::ConstCharPointer: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) { auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); - auto s = QString::fromStdString(ReadConstString(offset)); + auto s = QString::fromStdString(ReadConstString(address)); // [Known Issue]: Now way to safely determinate allowed lenght, so we just allow as much characters as there is already - auto dialog = new DialogEditString(fieldName, s, offset, s.length(), dataType, this); + auto dialog = new DialogEditString(fieldName, s, address, s.length(), dataType, this); dialog->exec(); } break; } case MemoryFieldType::UTF8StringFixedSize: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) { int size = getDataFrom(index, gsColField, gsRoleSize).toInt(); auto fieldName = getDataFrom(index, gsColField, gsRoleUID).toString(); auto stringData = std::make_unique(size); - Script::Memory::Read(offset, stringData.get(), size, nullptr); + Script::Memory::Read(address, stringData.get(), size, nullptr); auto s = QString::fromUtf8(stringData.get()); - auto dialog = new DialogEditString(fieldName, s, offset, size - 1, dataType, this); + auto dialog = new DialogEditString(fieldName, s, address, size - 1, dataType, this); dialog->exec(); } break; } case MemoryFieldType::EntityList: { - auto offset = clickedItem->data(gsRoleMemoryAddress).toULongLong(); - if (offset != 0) + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) + { + getToolbar()->showEntityList(address); + } + break; + } + case MemoryFieldType::OldStdList: + { + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) { - getToolbar()->showEntityList(offset); + auto typeName = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerFirstParameterType)); + //getToolbar()->showStdList(address, typeName, true); } break; } diff --git a/src/Views/ViewCharacterDB.cpp b/src/Views/ViewCharacterDB.cpp index 9a8d747..34d0411 100644 --- a/src/Views/ViewCharacterDB.cpp +++ b/src/Views/ViewCharacterDB.cpp @@ -5,7 +5,7 @@ #include "Spelunky2.h" #include -S2Plugin::ViewCharacterDB::ViewCharacterDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::CharacterDB, parent) +S2Plugin::ViewCharacterDB::ViewCharacterDB(QWidget* parent) : AbstractDatabaseView(MemoryFieldType::CharacterDB, parent) { setWindowTitle("Character DB"); auto& charDB = Spelunky2::get()->get_CharacterDB(); diff --git a/src/Views/ViewEntityDB.cpp b/src/Views/ViewEntityDB.cpp index d9825b6..cb2c7e0 100644 --- a/src/Views/ViewEntityDB.cpp +++ b/src/Views/ViewEntityDB.cpp @@ -7,7 +7,7 @@ #include "Spelunky2.h" #include -S2Plugin::ViewEntityDB::ViewEntityDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::EntityDB, parent) +S2Plugin::ViewEntityDB::ViewEntityDB(QWidget* parent) : AbstractDatabaseView(MemoryFieldType::EntityDB, parent) { auto config = Configuration::get(); setWindowTitle(QString("Entity DB (%1 entities)").arg(config->entityList().count())); diff --git a/src/Views/ViewParticleDB.cpp b/src/Views/ViewParticleDB.cpp index 08d1f04..cc66ba2 100644 --- a/src/Views/ViewParticleDB.cpp +++ b/src/Views/ViewParticleDB.cpp @@ -5,7 +5,7 @@ #include "Spelunky2.h" #include -S2Plugin::ViewParticleDB::ViewParticleDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::ParticleDB, parent) +S2Plugin::ViewParticleDB::ViewParticleDB(QWidget* parent) : AbstractDatabaseView(MemoryFieldType::ParticleDB, parent) { auto& particleEmitters = Configuration::get()->particleEmittersList(); setWindowTitle(QString("Particle DB (%1 particles)").arg(particleEmitters.count())); diff --git a/src/Views/ViewTextureDB.cpp b/src/Views/ViewTextureDB.cpp index 3973ac3..67991f9 100644 --- a/src/Views/ViewTextureDB.cpp +++ b/src/Views/ViewTextureDB.cpp @@ -6,7 +6,7 @@ #include "Spelunky2.h" #include -S2Plugin::ViewTextureDB::ViewTextureDB(QWidget* parent) : WidgetDatabaseView(MemoryFieldType::TextureDB, parent) +S2Plugin::ViewTextureDB::ViewTextureDB(QWidget* parent) : AbstractDatabaseView(MemoryFieldType::TextureDB, parent) { auto& textureDB = Spelunky2::get()->get_TextureDB(); setWindowTitle(QString("Texture DB (%1 textures)").arg(textureDB.count())); From 910ecf0d86c7bb7b2a9716a33e92385308accad2 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:18:04 +0200 Subject: [PATCH 07/20] add this giant array that sits below ai targets --- resources/Spelunky2.json | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 16d6a01..79e981d 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -3330,7 +3330,7 @@ { "field": "layer1", "type": "LayerPointer" }, { "field": "logic", "type": "LogicList" }, { "field": "quests", "type": "QuestsInfoPointer" }, - { "field": "ai_targets", "type": "Array", "pointer": true, "length": 128, "arraytype": "AITarget" }, + { "field": "ai_targets", "type": "UnknownPointer1", "pointer": true }, { "field": "liquid_physics", "type": "LiquidPhysicsPointer" }, { "field": "particle_emitters", @@ -3419,6 +3419,22 @@ "comment": "padding probably" } ], + "UnknownStruct": [ + { "field": "unknown1", "type": "UnsignedByte" }, + { "field": "unknown2", "type": "UnsignedByte" }, + { "field": "unknown3", "type": "UnsignedByte" }, + { "field": "unknown4", "type": "UnsignedByte" }, + { "field": "unknown5", "type": "UnsignedByte" }, + { "field": "unknown6", "type": "UnsignedByte" }, + { "field": "unknown7", "type": "UnsignedByte" }, + { "field": "unknown8", "type": "UnsignedByte" }, + { "field": "unknown9", "type": "UnsignedByte" } + ], + "UnknownPointer1": [ + { "field": "ai_targets", "type": "Array", "length": 128, "arraytype": "AITarget" }, + { "field": "unknown1", "type": "Array", "length": 19200, "arraytype": "UnknownStruct" }, + { "field": "unknown2", "type": "UnsignedQword", "comment": "end? hopefully" } + ], "BackLayerRelated": [ { "field": "entity", "type": "EntityPointer" }, { "field": "layer_dest", "type": "UnsignedByte" }, From 56db4ad637718809b0436a0aba2ebd3e68ccd147 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:18:46 +0200 Subject: [PATCH 08/20] add template for container view, improve the StdSet a little bit --- CMakeLists.txt | 2 + include/QtHelpers/AbstractContainerView.h | 25 +++++++++ include/Views/ViewEntityList.h | 14 ++--- include/Views/ViewStdMap.h | 14 ++--- include/Views/ViewStdVector.h | 19 ++----- src/QtHelpers/AbstractContainerView.cpp | 42 +++++++++++++++ src/QtHelpers/TreeViewMemoryFields.cpp | 4 +- src/Views/ViewEntityList.cpp | 43 ++------------- src/Views/ViewStdMap.cpp | 65 +++++------------------ src/Views/ViewStdVector.cpp | 48 ++--------------- 10 files changed, 107 insertions(+), 169 deletions(-) create mode 100644 include/QtHelpers/AbstractContainerView.h create mode 100644 src/QtHelpers/AbstractContainerView.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bea818..961e682 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ x64dbg_plugin(${PROJECT_NAME} include/QtHelpers/WidgetSamplesPlot.h include/QtHelpers/ItemModelLoggerSamples.h include/QtHelpers/AbstractDatabaseView.h + include/QtHelpers/AbstractContainerView.h include/QtHelpers/WidgetAutorefresh.h include/QtHelpers/LongLongSpinBox.h src/Spelunky2.cpp @@ -153,6 +154,7 @@ x64dbg_plugin(${PROJECT_NAME} src/QtHelpers/WidgetSamplesPlot.cpp src/QtHelpers/ItemModelLoggerSamples.cpp src/QtHelpers/AbstractDatabaseView.cpp + src/QtHelpers/AbstractContainerView.cpp src/QtHelpers/WidgetAutorefresh.cpp ${CMAKE_CURRENT_BINARY_DIR}/include/pluginconfig.h resources/spelunky2.qrc diff --git a/include/QtHelpers/AbstractContainerView.h b/include/QtHelpers/AbstractContainerView.h new file mode 100644 index 0000000..5b64b17 --- /dev/null +++ b/include/QtHelpers/AbstractContainerView.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include +#include +#include + +namespace S2Plugin +{ + class TreeViewMemoryFields; + + class AbstractContainerView : public QWidget + { + Q_OBJECT + public: + AbstractContainerView(QWidget* parent = nullptr); + + protected: + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + virtual void reloadContainer() = 0; + + TreeViewMemoryFields* mMainTreeView; + }; +} // namespace S2Plugin diff --git a/include/Views/ViewEntityList.h b/include/Views/ViewEntityList.h index 0617b53..a99ee6a 100644 --- a/include/Views/ViewEntityList.h +++ b/include/Views/ViewEntityList.h @@ -1,30 +1,22 @@ #pragma once -#include +#include "QtHelpers/AbstractContainerView.h" #include #include #include namespace S2Plugin { - class TreeViewMemoryFields; - - class ViewEntityList : public QWidget + class ViewEntityList : public AbstractContainerView { Q_OBJECT public: ViewEntityList(uintptr_t address, QWidget* parent = nullptr); protected: - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - - private slots: - void refreshEntityListContents(); + void reloadContainer() override; private: uintptr_t mEntityListAddress; - - TreeViewMemoryFields* mMainTreeView; }; } // namespace S2Plugin diff --git a/include/Views/ViewStdMap.h b/include/Views/ViewStdMap.h index 38f95d2..23576f1 100644 --- a/include/Views/ViewStdMap.h +++ b/include/Views/ViewStdMap.h @@ -1,27 +1,21 @@ #pragma once -#include +#include "QtHelpers/AbstractContainerView.h" #include #include #include namespace S2Plugin { - class TreeViewMemoryFields; - class ViewStdMap : public QWidget + class ViewStdMap : public AbstractContainerView { Q_OBJECT public: ViewStdMap(const std::string& keytypeName, const std::string& valuetypeName, uintptr_t address, QWidget* parent = nullptr); protected: - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - - private slots: - void refreshMapContents(); - void refreshData(); + void reloadContainer() override; private: std::string mMapKeyType; @@ -31,7 +25,5 @@ namespace S2Plugin size_t mMapValueTypeSize; uint8_t mMapKeyAlignment; uint8_t mMapValueAlignment; - - TreeViewMemoryFields* mMainTreeView; }; } // namespace S2Plugin diff --git a/include/Views/ViewStdVector.h b/include/Views/ViewStdVector.h index 67de2ac..6c27176 100644 --- a/include/Views/ViewStdVector.h +++ b/include/Views/ViewStdVector.h @@ -1,33 +1,24 @@ #pragma once -#include +#include "QtHelpers/AbstractContainerView.h" #include #include #include namespace S2Plugin { - class TreeViewMemoryFields; - - class ViewStdVector : public QWidget + class ViewStdVector : public AbstractContainerView { Q_OBJECT public: - ViewStdVector(const std::string& vectorType, uintptr_t vectorOffset, QWidget* parent = nullptr); + ViewStdVector(const std::string& vectorType, uintptr_t vectoraddr, QWidget* parent = nullptr); protected: - QSize sizeHint() const override; - QSize minimumSizeHint() const override; - - private slots: - void refreshVectorContents(); - void refreshData(); + void reloadContainer() override; private: std::string mVectorType; - uintptr_t mVectorOffset; + uintptr_t mVectorAddress; size_t mVectorTypeSize; - - TreeViewMemoryFields* mMainTreeView; }; } // namespace S2Plugin diff --git a/src/QtHelpers/AbstractContainerView.cpp b/src/QtHelpers/AbstractContainerView.cpp new file mode 100644 index 0000000..ae1aa1a --- /dev/null +++ b/src/QtHelpers/AbstractContainerView.cpp @@ -0,0 +1,42 @@ +#include "QtHelpers/AbstractContainerView.h" + +#include "Configuration.h" +#include "Data/EntityList.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetAutorefresh.h" +#include "QtPlugin.h" +#include +#include +#include + +S2Plugin::AbstractContainerView::AbstractContainerView(QWidget* parent) : QWidget(parent) +{ + setWindowIcon(getCavemanIcon()); + + auto mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(5); + auto refreshLayout = new QHBoxLayout(); + mainLayout->addLayout(refreshLayout); + + auto refreshVectorButton = new QPushButton("Reload", this); + QObject::connect(refreshVectorButton, &QPushButton::clicked, this, &AbstractContainerView::reloadContainer); + refreshLayout->addWidget(refreshVectorButton); + + auto autoRefresh = new WidgetAutorefresh(300, this); + refreshLayout->addWidget(autoRefresh); + + mMainTreeView = new TreeViewMemoryFields(this); + QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, mMainTreeView, static_cast(&TreeViewMemoryFields::updateTree)); + mainLayout->addWidget(mMainTreeView); + autoRefresh->toggleAutoRefresh(true); +} + +QSize S2Plugin::AbstractContainerView::sizeHint() const +{ + return QSize(750, 550); +} + +QSize S2Plugin::AbstractContainerView::minimumSizeHint() const +{ + return QSize(150, 150); +} diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index b5e0c64..aabdbec 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -154,6 +154,8 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& typeName += QString("%1[%2]").arg(QString::fromStdString(field.firstParameterType)).arg(field.numberOfElements); else if (field.type == MemoryFieldType::EntitySubclass || field.type == MemoryFieldType::DefaultStructType) typeName += QString::fromStdString(field.jsonName); + else if (field.type == MemoryFieldType::StdMap && field.secondParameterType.empty()) // exception + typeName += "StdSet"; else if (auto str = Configuration::getTypeDisplayName(field.type); !str.empty()) typeName += QString::fromUtf8(str.data(), static_cast(str.size())); else @@ -2572,7 +2574,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) if (address != 0) { auto typeName = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerFirstParameterType)); - //getToolbar()->showStdList(address, typeName, true); + // getToolbar()->showStdList(address, typeName, true); } break; } diff --git a/src/Views/ViewEntityList.cpp b/src/Views/ViewEntityList.cpp index 29d4bd3..b887a55 100644 --- a/src/Views/ViewEntityList.cpp +++ b/src/Views/ViewEntityList.cpp @@ -3,44 +3,21 @@ #include "Configuration.h" #include "Data/EntityList.h" #include "QtHelpers/TreeViewMemoryFields.h" -#include "QtHelpers/WidgetAutorefresh.h" -#include "QtPlugin.h" -#include #include -#include -S2Plugin::ViewEntityList::ViewEntityList(uintptr_t address, QWidget* parent) : mEntityListAddress(address), QWidget(parent) +S2Plugin::ViewEntityList::ViewEntityList(uintptr_t address, QWidget* parent) : mEntityListAddress(address), AbstractContainerView(parent) { - setWindowIcon(getCavemanIcon()); setWindowTitle("EntityList"); - - auto mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(5); - auto refreshLayout = new QHBoxLayout(); - mainLayout->addLayout(refreshLayout); - - auto refreshVectorButton = new QPushButton("Refresh list", this); - QObject::connect(refreshVectorButton, &QPushButton::clicked, this, &ViewEntityList::refreshEntityListContents); - refreshLayout->addWidget(refreshVectorButton); - - auto autoRefresh = new WidgetAutorefresh(300, this); - refreshLayout->addWidget(autoRefresh); - - mMainTreeView = new TreeViewMemoryFields(this); - QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, mMainTreeView, static_cast(&TreeViewMemoryFields::updateTree)); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment).disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress); - mainLayout->addWidget(mMainTreeView); - autoRefresh->toggleAutoRefresh(true); - refreshEntityListContents(); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment); //.disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress); + reloadContainer(); } -void S2Plugin::ViewEntityList::refreshEntityListContents() +void S2Plugin::ViewEntityList::reloadContainer() { mMainTreeView->clear(); EntityList entityList{mEntityListAddress}; - // TODO using custom view instead of memory view would improve performance auto entities = entityList.entities(); auto uids = entityList.getAllUids(); for (size_t idx = 0; idx < entityList.size(); ++idx) @@ -49,7 +26,7 @@ void S2Plugin::ViewEntityList::refreshEntityListContents() entityField.name = "uid_" + std::to_string(uids[idx]); entityField.isPointer = true; entityField.type = MemoryFieldType::EntityPointer; - mMainTreeView->addMemoryField(entityField, {}, entities + idx * sizeof(uintptr_t), 0); + mMainTreeView->addMemoryField(entityField, {}, entities + idx * sizeof(uintptr_t), idx * sizeof(uintptr_t)); } mMainTreeView->updateTableHeader(); @@ -60,13 +37,3 @@ void S2Plugin::ViewEntityList::refreshEntityListContents() mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(0, 0, true); } - -QSize S2Plugin::ViewEntityList::sizeHint() const -{ - return QSize(750, 550); -} - -QSize S2Plugin::ViewEntityList::minimumSizeHint() const -{ - return QSize(150, 150); -} diff --git a/src/Views/ViewStdMap.cpp b/src/Views/ViewStdMap.cpp index 9c9e73b..704031c 100644 --- a/src/Views/ViewStdMap.cpp +++ b/src/Views/ViewStdMap.cpp @@ -3,16 +3,11 @@ #include "Configuration.h" #include "Data/StdMap.h" #include "QtHelpers/TreeViewMemoryFields.h" -#include "QtHelpers/WidgetAutorefresh.h" -#include "QtPlugin.h" -#include "Spelunky2.h" #include "pluginmain.h" -#include #include -#include S2Plugin::ViewStdMap::ViewStdMap(const std::string& keytypeName, const std::string& valuetypeName, uintptr_t address, QWidget* parent) - : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mMapAddress(address), QWidget(parent) + : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mMapAddress(address), AbstractContainerView(parent) { auto config = Configuration::get(); mMapKeyTypeSize = config->getTypeSize(keytypeName); @@ -21,35 +16,16 @@ S2Plugin::ViewStdMap::ViewStdMap(const std::string& keytypeName, const std::stri mMapKeyAlignment = config->getAlingment(keytypeName); mMapValueAlignment = config->getAlingment(valuetypeName); - setWindowIcon(getCavemanIcon()); - if (mMapValueTypeSize == 0) setWindowTitle(QString("std::set<%1>").arg(QString::fromStdString(keytypeName))); else setWindowTitle(QString("std::map<%1, %2>").arg(QString::fromStdString(keytypeName), QString::fromStdString(valuetypeName))); - auto mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(5); - auto refreshLayout = new QHBoxLayout(); - mainLayout->addLayout(refreshLayout); - - auto refreshMapButton = new QPushButton("Refresh map", this); - QObject::connect(refreshMapButton, &QPushButton::clicked, this, &ViewStdMap::refreshMapContents); - refreshLayout->addWidget(refreshMapButton); - - auto autoRefresh = new WidgetAutorefresh(100, this); - QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, this, &ViewStdMap::refreshData); - refreshLayout->addWidget(autoRefresh); - - mMainTreeView = new TreeViewMemoryFields(this); - mMainTreeView->setEnableChangeHighlighting(false); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColComment); - mainLayout->addWidget(mMainTreeView); - autoRefresh->toggleAutoRefresh(true); - refreshMapContents(); + reloadContainer(); } -void S2Plugin::ViewStdMap::refreshMapContents() +void S2Plugin::ViewStdMap::reloadContainer() { if (!Script::Memory::IsValidPtr(Script::Memory::ReadQword(mMapAddress))) return; @@ -117,16 +93,18 @@ void S2Plugin::ViewStdMap::refreshMapContents() parent_field.type = MemoryFieldType::Dummy; for (int x = 0; _cur != _end && x < 300; ++x, ++_cur) { - QStandardItem* parent{nullptr}; - parent_field.name = "obj_" + std::to_string(x); - parent = mMainTreeView->addMemoryField(parent_field, parent_field.name, 0, 0); - - mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0, 0, parent); - if (mMapValueTypeSize == 0) // StdSet - continue; - - mMainTreeView->addMemoryField(value_field, value_field.name, _cur.value_ptr(), 0, 0, parent); + { + key_field.name = "key_" + std::to_string(x); + mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0); + } + else // StdMap + { + parent_field.name = "obj_" + std::to_string(x); + QStandardItem* parent = mMainTreeView->addMemoryField(parent_field, parent_field.name, 0, 0); + mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0, 0, parent); + mMainTreeView->addMemoryField(value_field, value_field.name, _cur.value_ptr(), 0, 0, parent); + } } mMainTreeView->updateTableHeader(); mMainTreeView->setColumnWidth(gsColField, 145); @@ -136,18 +114,3 @@ void S2Plugin::ViewStdMap::refreshMapContents() mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(0, 0, true); } - -void S2Plugin::ViewStdMap::refreshData() -{ - mMainTreeView->updateTree(); -} - -QSize S2Plugin::ViewStdMap::sizeHint() const -{ - return QSize(750, 550); -} - -QSize S2Plugin::ViewStdMap::minimumSizeHint() const -{ - return QSize(150, 150); -} diff --git a/src/Views/ViewStdVector.cpp b/src/Views/ViewStdVector.cpp index b8d5f4e..fc3ea62 100644 --- a/src/Views/ViewStdVector.cpp +++ b/src/Views/ViewStdVector.cpp @@ -2,48 +2,25 @@ #include "Configuration.h" #include "QtHelpers/TreeViewMemoryFields.h" -#include "QtHelpers/WidgetAutorefresh.h" -#include "QtPlugin.h" -#include "Spelunky2.h" #include "pluginmain.h" -#include #include -#include -S2Plugin::ViewStdVector::ViewStdVector(const std::string& vectorType, uintptr_t vectorOffset, QWidget* parent) : mVectorType(vectorType), mVectorOffset(vectorOffset), QWidget(parent) +S2Plugin::ViewStdVector::ViewStdVector(const std::string& vectorType, uintptr_t vectorAddr, QWidget* parent) : mVectorType(vectorType), mVectorAddress(vectorAddr), AbstractContainerView(parent) { mVectorTypeSize = Configuration::get()->getTypeSize(mVectorType); - setWindowIcon(getCavemanIcon()); setWindowTitle(QString("std::vector<%1>").arg(QString::fromStdString(vectorType))); - - auto mainLayout = new QVBoxLayout(this); - mainLayout->setMargin(5); - auto refreshLayout = new QHBoxLayout(); - mainLayout->addLayout(refreshLayout); - - auto refreshVectorButton = new QPushButton("Refresh vector", this); - QObject::connect(refreshVectorButton, &QPushButton::clicked, this, &ViewStdVector::refreshVectorContents); - refreshLayout->addWidget(refreshVectorButton); - - auto autoRefresh = new WidgetAutorefresh(100, this); - refreshLayout->addWidget(autoRefresh); - QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, this, &ViewStdVector::refreshData); - - mMainTreeView = new TreeViewMemoryFields(this); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment); - mainLayout->addWidget(mMainTreeView); - autoRefresh->toggleAutoRefresh(true); - refreshVectorContents(); + reloadContainer(); } -void S2Plugin::ViewStdVector::refreshVectorContents() +void S2Plugin::ViewStdVector::reloadContainer() { auto config = Configuration::get(); mMainTreeView->clear(); - uintptr_t vectorBegin = Script::Memory::ReadQword(mVectorOffset); - uintptr_t vectorEnd = Script::Memory::ReadQword(mVectorOffset + sizeof(uintptr_t)); + uintptr_t vectorBegin = Script::Memory::ReadQword(mVectorAddress); + uintptr_t vectorEnd = Script::Memory::ReadQword(mVectorAddress + sizeof(uintptr_t)); if (vectorBegin == vectorEnd) return; @@ -93,18 +70,3 @@ void S2Plugin::ViewStdVector::refreshVectorContents() mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(0, 0, true); } - -void S2Plugin::ViewStdVector::refreshData() -{ - mMainTreeView->updateTree(); -} - -QSize S2Plugin::ViewStdVector::sizeHint() const -{ - return QSize(750, 550); -} - -QSize S2Plugin::ViewStdVector::minimumSizeHint() const -{ - return QSize(150, 150); -} From 5cbf28875e34f91b31943247cc6a66cde57fdba0 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 2 Jun 2024 19:53:37 +0200 Subject: [PATCH 09/20] add view for StdList --- CMakeLists.txt | 2 + include/Views/ViewStdList.h | 24 +++++++++ include/Views/ViewToolbar.h | 1 + resources/Spelunky2.json | 4 +- src/QtHelpers/TreeViewMemoryFields.cpp | 3 +- src/Views/ViewStdList.cpp | 67 ++++++++++++++++++++++++++ src/Views/ViewToolbar.cpp | 9 ++++ 7 files changed, 107 insertions(+), 3 deletions(-) create mode 100644 include/Views/ViewStdList.h create mode 100644 src/Views/ViewStdList.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 961e682..710af25 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Views/ViewJournalPage.h include/Views/ViewThreads.h include/Views/ViewStdMap.h + include/Views/ViewStdList.h include/Views/ViewEntityList.h include/QtHelpers/StyledItemDelegateHTML.h include/QtHelpers/StyledItemDelegateColorPicker.h @@ -133,6 +134,7 @@ x64dbg_plugin(${PROJECT_NAME} src/Views/ViewVirtualFunctions.cpp src/Views/ViewStdVector.cpp src/Views/ViewStdMap.cpp + src/Views/ViewStdList.cpp src/Views/ViewJournalPage.cpp src/Views/ViewThreads.cpp src/Views/ViewEntityList.cpp diff --git a/include/Views/ViewStdList.h b/include/Views/ViewStdList.h new file mode 100644 index 0000000..fa4d805 --- /dev/null +++ b/include/Views/ViewStdList.h @@ -0,0 +1,24 @@ +#pragma once + +#include "QtHelpers/AbstractContainerView.h" +#include +#include +#include + +namespace S2Plugin +{ + class ViewStdList : public AbstractContainerView + { + Q_OBJECT + public: + ViewStdList(uintptr_t addr, const std::string& valueType, bool oldType = false, QWidget* parent = nullptr); + + protected: + void reloadContainer() override; + + private: + std::string mListType; + uintptr_t mListAddress; + bool mOldType; + }; +} // namespace S2Plugin diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index d5a586d..c038bf0 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -29,6 +29,7 @@ namespace S2Plugin void showArray(uintptr_t address, std::string name, std::string arrayTypeName, size_t length); void showMatrix(uintptr_t address, std::string name, std::string arrayTypeName, size_t rows, size_t columns); void showEntityList(uintptr_t address); + void showStdList(uintptr_t address, std::string typeName, bool oldType = false); public slots: ViewEntityDB* showEntityDB(); diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 79e981d..1f49b87 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -7858,11 +7858,11 @@ "type": "UnsignedDword", "comment": "padding probably" }, - { "field": "liquid_ids", "type": "OldStdList" }, + { "field": "liquid_ids", "type": "OldStdList", "valuetype": "UnsignedDword" }, { "field": "unknown44", "type": "OldStdList", - "comment": "all of them are -1" + "valuetype": "Dword" }, { "field": "list", diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index aabdbec..2857d9e 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -280,6 +280,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& addMemoryFields(config->typeFieldsOfDefaultStruct("ThemeInfoPointer"), fieldNameOverride, 0, 0, deltaPrefixCount + 1, returnField); break; } + case MemoryFieldType::OldStdList: case MemoryFieldType::StdVector: { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); @@ -2574,7 +2575,7 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) if (address != 0) { auto typeName = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerFirstParameterType)); - // getToolbar()->showStdList(address, typeName, true); + getToolbar()->showStdList(address, typeName, true); } break; } diff --git a/src/Views/ViewStdList.cpp b/src/Views/ViewStdList.cpp new file mode 100644 index 0000000..7c3e904 --- /dev/null +++ b/src/Views/ViewStdList.cpp @@ -0,0 +1,67 @@ +#include "Views/ViewStdList.h" + +#include "Configuration.h" +#include "Data/OldStdList.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "pluginmain.h" +#include + +S2Plugin::ViewStdList::ViewStdList(uintptr_t addr, const std::string& valueType, bool oldType, QWidget* parent) + : mListType(valueType), mListAddress(addr), mOldType(oldType), AbstractContainerView(parent) +{ + setWindowTitle(QString("std::list<%1>").arg(QString::fromStdString(mListType))); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment).disable(gsColMemoryAddressDelta); + reloadContainer(); +} + +void S2Plugin::ViewStdList::reloadContainer() +{ + auto config = Configuration::get(); + mMainTreeView->clear(); + + MemoryField field; + if (config->isPermanentPointer(mListType)) + { + field.type = MemoryFieldType::DefaultStructType; + field.jsonName = mListType; + field.isPointer = true; + } + else if (config->isJsonStruct(mListType)) + { + field.type = MemoryFieldType::DefaultStructType; + field.jsonName = mListType; + } + else if (auto type = config->getBuiltInType(mListType); type != MemoryFieldType::None) + { + field.type = type; + if (Configuration::isPointerType(type)) + field.isPointer = true; + } + else + { + dprintf("unknown type in ViewStdList::refreshVectorContents() (%s)\n", mListType.c_str()); + return; + } + if (mOldType) + { + OldStdList theList{mListAddress}; + size_t x = 0; + for (auto val_addr : theList) + { + field.name = "val_" + std::to_string(x++); + mMainTreeView->addMemoryField(field, field.name, val_addr, 0); + } + } + else + { + // TODO new type implement, if found + } + + mMainTreeView->updateTableHeader(); + mMainTreeView->setColumnWidth(gsColField, 145); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 300); + mMainTreeView->updateTree(0, 0, true); +} diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index 974e75e..d652720 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -11,6 +11,7 @@ #include "Views/ViewLevelGen.h" #include "Views/ViewLogger.h" #include "Views/ViewParticleDB.h" +#include "Views/ViewStdList.h" #include "Views/ViewStdMap.h" #include "Views/ViewStdVector.h" #include "Views/ViewStringsTable.h" @@ -209,6 +210,14 @@ void S2Plugin::ViewToolbar::showEntityList(uintptr_t address) win->setAttribute(Qt::WA_DeleteOnClose); } +void S2Plugin::ViewToolbar::showStdList(uintptr_t address, std::string valueType, bool isOldType) +{ + auto w = new ViewStdList(address, std::move(valueType), isOldType); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + // // slots: // From ed37b0ea33730e4c29dce7456dc36633258128cf Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 2 Jun 2024 22:58:57 +0200 Subject: [PATCH 10/20] add current std::list implementation --- CMakeLists.txt | 2 +- include/Configuration.h | 1 + include/Data/{OldStdList.h => StdList.h} | 51 ++++++++++++++++++++++++ src/Configuration.cpp | 5 ++- src/QtHelpers/TreeViewMemoryFields.cpp | 25 +++++++++--- src/Views/ViewStdList.cpp | 10 ++++- 6 files changed, 85 insertions(+), 9 deletions(-) rename include/Data/{OldStdList.h => StdList.h} (70%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 710af25..105dae3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Data/StdString.h include/Data/StdMap.h include/Data/EntityList.h - include/Data/OldStdList.h + include/Data/StdList.h include/Views/ViewToolbar.h include/Views/ViewEntityDB.h include/Views/ViewParticleDB.h diff --git a/include/Configuration.h b/include/Configuration.h index be2e17d..cb4003c 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -131,6 +131,7 @@ namespace S2Plugin Matrix, EntityList, OldStdList, + StdList, }; struct VirtualFunction diff --git a/include/Data/OldStdList.h b/include/Data/StdList.h similarity index 70% rename from include/Data/OldStdList.h rename to include/Data/StdList.h index 2bd2a84..f50c242 100644 --- a/include/Data/OldStdList.h +++ b/include/Data/StdList.h @@ -98,4 +98,55 @@ namespace S2Plugin private: Node _end; }; + + // the current standard + class StdList + { + public: + using Node = OldStdList::Node; + + StdList(uintptr_t address) + { + uintptr_t data[2]; + Script::Memory::Read(address, &data, 2 * sizeof(uintptr_t), nullptr); + mHead = data[0]; + mSize = data[1]; + } + Node begin() const + { + return mHead.next(); + } + Node end() const + { + return mHead; + } + size_t size() const + { + return mSize; + } + bool empty() const + { + return mSize == 0; + } + Node cbegin() const + { + return begin(); + } + Node cend() const + { + return end(); + } + Node back() const + { + return mHead.prev(); + } + Node front() const + { + return mHead.next(); + } + + private: + Node mHead{0}; + size_t mSize; + }; }; // namespace S2Plugin diff --git a/src/Configuration.cpp b/src/Configuration.cpp index bc6ee50..47f1f80 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -113,6 +113,7 @@ namespace S2Plugin {MemoryFieldType::StdString, "StdString", "std::string", "StdString", 32, false}, {MemoryFieldType::StdWstring, "StdWstring", "std::wstring", "StdWstring", 32, false}, {MemoryFieldType::OldStdList, "OldStdList", "std::pair", "OldStdList", 16, false}, // can't use std::list representation since the standard was changed + {MemoryFieldType::StdList, "StdList", "std::list", "StdList", 16, false}, // Game Main structs {MemoryFieldType::GameManager, "GameManager", "", "GameManager", 0, false}, {MemoryFieldType::State, "State", "", "State", 0, false}, @@ -344,6 +345,7 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman break; } case MemoryFieldType::OldStdList: + case MemoryFieldType::StdList: { if (field.contains("valuetype")) { @@ -352,7 +354,7 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman else { memField.firstParameterType = "UnsignedQword"; - dprintf("no valuetype specified for OldStdList (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); + dprintf("no valuetype specified for StdList (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); } break; } @@ -825,6 +827,7 @@ int S2Plugin::Configuration::getAlingment(const std::string& typeName) const case MemoryFieldType::UnsignedQword: case MemoryFieldType::Double: case MemoryFieldType::OldStdList: + case MemoryFieldType::StdList: case MemoryFieldType::EntityList: return sizeof(uintptr_t); } diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 2857d9e..f4720d4 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -4,9 +4,9 @@ #include "Data/CharacterDB.h" #include "Data/Entity.h" #include "Data/EntityDB.h" -#include "Data/OldStdList.h" #include "Data/ParticleDB.h" #include "Data/State.h" +#include "Data/StdList.h" #include "Data/StdString.h" #include "Data/StringsTable.h" #include "Data/TextureDB.h" @@ -281,6 +281,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& break; } case MemoryFieldType::OldStdList: + case MemoryFieldType::StdList: case MemoryFieldType::StdVector: { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); @@ -1960,6 +1961,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::StdList: case MemoryFieldType::OldStdList: { std::optional> value; @@ -1983,8 +1985,9 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional itemField->setBackground(highlightColor); itemValue->setData(QVariant::fromValue(value.value()), S2Plugin::gsRoleRawValue); - OldStdList list{valueMemoryOffset}; - if (list.empty()) + bool empty = (fieldType == MemoryFieldType::OldStdList && OldStdList{valueMemoryOffset}.empty()) || (fieldType == MemoryFieldType::StdList && StdList{valueMemoryOffset}.empty()); + + if (empty) itemValue->setData("Show contents (empty)", Qt::DisplayRole); else itemValue->setData("Show contents", Qt::DisplayRole); @@ -2014,8 +2017,10 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { itemComparisonValue->setData(QVariant::fromValue(comparisonValue.value()), S2Plugin::gsRoleRawValue); - OldStdList list{valueComparisonMemoryOffset}; - if (list.empty()) + bool empty = (fieldType == MemoryFieldType::OldStdList && OldStdList{valueComparisonMemoryOffset}.empty()) || + (fieldType == MemoryFieldType::StdList && StdList{valueComparisonMemoryOffset}.empty()); + + if (empty) itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); else itemComparisonValue->setData("Show contents", Qt::DisplayRole); @@ -2579,6 +2584,16 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } break; } + case MemoryFieldType::StdList: + { + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) + { + auto typeName = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerFirstParameterType)); + getToolbar()->showStdList(address, typeName); + } + break; + } } emit memoryFieldValueUpdated(index.row(), clickedItem->parent()); } diff --git a/src/Views/ViewStdList.cpp b/src/Views/ViewStdList.cpp index 7c3e904..be020e5 100644 --- a/src/Views/ViewStdList.cpp +++ b/src/Views/ViewStdList.cpp @@ -1,7 +1,7 @@ #include "Views/ViewStdList.h" #include "Configuration.h" -#include "Data/OldStdList.h" +#include "Data/StdList.h" #include "QtHelpers/TreeViewMemoryFields.h" #include "pluginmain.h" #include @@ -54,7 +54,13 @@ void S2Plugin::ViewStdList::reloadContainer() } else { - // TODO new type implement, if found + StdList theList{mListAddress}; + size_t x = 0; + for (auto val_addr : theList) + { + field.name = "val_" + std::to_string(x++); + mMainTreeView->addMemoryField(field, field.name, val_addr, 0); + } } mMainTreeView->updateTableHeader(); From 7545cd7c1b64b6d0be68178ae684f7122716f4dc Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:13:57 +0200 Subject: [PATCH 11/20] implement unordered map and it's view --- CMakeLists.txt | 3 + include/Configuration.h | 7 +- include/Data/StdList.h | 26 ++-- include/Data/StdUnorderedMap.h | 128 ++++++++++++++++++ include/Views/ViewStdMap.h | 2 +- include/Views/ViewStdUnorderedMap.h | 27 ++++ include/Views/ViewStdVector.h | 2 +- include/Views/ViewToolbar.h | 1 + include/Views/ViewVirtualFunctions.h | 2 +- resources/Spelunky2.json | 107 +++++++++++++-- src/Configuration.cpp | 179 +++++++++++++++++-------- src/QtHelpers/TreeViewMemoryFields.cpp | 101 +++++++++++++- src/Views/ViewStdMap.cpp | 2 +- src/Views/ViewStdUnorderedMap.cpp | 113 ++++++++++++++++ src/Views/ViewStdVector.cpp | 2 +- src/Views/ViewToolbar.cpp | 15 ++- src/Views/ViewVirtualFunctions.cpp | 2 +- 17 files changed, 624 insertions(+), 95 deletions(-) create mode 100644 include/Data/StdUnorderedMap.h create mode 100644 include/Views/ViewStdUnorderedMap.h create mode 100644 src/Views/ViewStdUnorderedMap.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 105dae3..249993f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Data/StdMap.h include/Data/EntityList.h include/Data/StdList.h + include/Data/StdUnorderedMap.h include/Views/ViewToolbar.h include/Views/ViewEntityDB.h include/Views/ViewParticleDB.h @@ -79,6 +80,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Views/ViewJournalPage.h include/Views/ViewThreads.h include/Views/ViewStdMap.h + include/Views/ViewStdUnorderedMap.h include/Views/ViewStdList.h include/Views/ViewEntityList.h include/QtHelpers/StyledItemDelegateHTML.h @@ -134,6 +136,7 @@ x64dbg_plugin(${PROJECT_NAME} src/Views/ViewVirtualFunctions.cpp src/Views/ViewStdVector.cpp src/Views/ViewStdMap.cpp + src/Views/ViewStdUnorderedMap.cpp src/Views/ViewStdList.cpp src/Views/ViewJournalPage.cpp src/Views/ViewThreads.cpp diff --git a/include/Configuration.h b/include/Configuration.h index cb4003c..c336341 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -132,6 +132,7 @@ namespace S2Plugin EntityList, OldStdList, StdList, + StdUnorderedMap, }; struct VirtualFunction @@ -227,13 +228,15 @@ namespace S2Plugin static size_t getBuiltInTypeSize(MemoryFieldType type); static bool isPointerType(MemoryFieldType type); // unknown name will be created as DefaultStructType - MemoryField nameToMemoryField(const std::string& name) const; + MemoryField nameToMemoryField(const std::string& typeName) const; uintptr_t offsetForField(const std::vector& fields, std::string_view fieldUID, uintptr_t base_addr = 0) const; uintptr_t offsetForField(MemoryFieldType type, std::string_view fieldUID, uintptr_t base_addr = 0) const; // equivalent to alignof operator - int getAlingment(const std::string& type) const; + uint8_t getAlingment(const std::string& type) const; + uint8_t getAlingment(MemoryFieldType type) const; + uint8_t getAlingment(const MemoryField& type) const; bool isPermanentPointer(const std::string& type) const { return std::find(mPointerTypes.begin(), mPointerTypes.end(), type) != mPointerTypes.end(); diff --git a/include/Data/StdList.h b/include/Data/StdList.h index f50c242..14b0e6f 100644 --- a/include/Data/StdList.h +++ b/include/Data/StdList.h @@ -23,11 +23,11 @@ namespace S2Plugin { return Script::Memory::ReadQword(nodeAddress + sizeof(uintptr_t)); } - uintptr_t value_ptr() const + uintptr_t value_ptr() const noexcept { return nodeAddress + 2 * sizeof(uintptr_t); } - uintptr_t operator*() const + uintptr_t operator*() const noexcept { return value_ptr(); } @@ -53,16 +53,20 @@ namespace S2Plugin nodeAddress = prev().nodeAddress; return *this; } - bool operator==(const Node& other) const + bool operator==(const Node& other) const noexcept { return nodeAddress == other.nodeAddress; } - bool operator!=(const Node& other) const + bool operator!=(const Node& other) const noexcept { return nodeAddress != other.nodeAddress; } + uintptr_t address() const noexcept + { + return nodeAddress; + } - private: + protected: uintptr_t nodeAddress; }; @@ -70,7 +74,7 @@ namespace S2Plugin { return _end.next(); } - Node end() const + Node end() const noexcept { return _end; } @@ -82,7 +86,7 @@ namespace S2Plugin { return begin(); } - Node cend() const + Node cend() const noexcept { return end(); } @@ -116,15 +120,15 @@ namespace S2Plugin { return mHead.next(); } - Node end() const + Node end() const noexcept { return mHead; } - size_t size() const + size_t size() const noexcept { return mSize; } - bool empty() const + bool empty() const noexcept { return mSize == 0; } @@ -132,7 +136,7 @@ namespace S2Plugin { return begin(); } - Node cend() const + Node cend() const noexcept { return end(); } diff --git a/include/Data/StdUnorderedMap.h b/include/Data/StdUnorderedMap.h new file mode 100644 index 0000000..5105e50 --- /dev/null +++ b/include/Data/StdUnorderedMap.h @@ -0,0 +1,128 @@ +#pragma once + +#include "pluginmain.h" +#include + +namespace S2Plugin +{ + + constexpr size_t gsStdUnorderedMapSize = 64; + // std::unordered_map internally basically consists of a std::list (but with next/prev switched around) and std::vector + hash stuff + class StdUnorderedMap + { + public: + StdUnorderedMap(uintptr_t address, size_t keySize, uint8_t alignment) : _end(0, 0) + { + size_t offset = keySize + sizeof(uintptr_t) * 2; + + // dealing with the padding between key and value + switch (alignment) + { + case 0: + case 1: + break; + case 2: + offset = (offset + 1) & ~1; + break; + case 3: + case 4: + offset = (offset + 3) & ~3; + break; + case 5: + case 6: + case 7: + case 8: + default: + offset = (offset + 7) & ~7; + break; + } + uintptr_t data[2]; + Script::Memory::Read(address + sizeof(uintptr_t), &data, sizeof(data), nullptr); + _end.mNodeAddress = data[0]; + _end.mValueOffset = offset; + mSize = data[1]; + }; + + struct Node + { + Node(uintptr_t address, size_t valueOffset) : mNodeAddress(address), mValueOffset(valueOffset){}; + + uintptr_t value_ptr() const noexcept + { + return mNodeAddress + mValueOffset; + } + uintptr_t key_ptr() const noexcept + { + return mNodeAddress + sizeof(uintptr_t) * 2; + } + std::pair operator*() const noexcept + { + return std::pair(key_ptr(), value_ptr()); + } + uintptr_t valueOffset() const noexcept + { + return mValueOffset; + } + Node next() const + { + return Node(Script::Memory::ReadQword(mNodeAddress), mValueOffset); + } + Node prev() const + { + return Node(Script::Memory::ReadQword(mNodeAddress + sizeof(uintptr_t)), mValueOffset); + } + Node& operator++() + { + mNodeAddress = Script::Memory::ReadQword(mNodeAddress); + return *this; + } + Node& operator--() + { + mNodeAddress = Script::Memory::ReadQword(mNodeAddress + sizeof(uintptr_t)); + return *this; + } + bool operator==(const Node& other) const + { + return mNodeAddress == other.mNodeAddress; + } + bool operator!=(const Node& other) const + { + return mNodeAddress != other.mNodeAddress; + } + + private: + uintptr_t mNodeAddress; + size_t mValueOffset; + friend class StdUnorderedMap; + }; + + bool empty() const noexcept + { + return mSize == 0; + } + size_t size() const noexcept + { + return mSize; + } + Node begin() const + { + return _end.next(); + } + Node end() const noexcept + { + return _end; + } + Node cbegin() const + { + return _end.next(); + } + Node cend() const noexcept + { + return _end; + } + + private: + Node _end; + size_t mSize; + }; +} // namespace S2Plugin diff --git a/include/Views/ViewStdMap.h b/include/Views/ViewStdMap.h index 23576f1..3574500 100644 --- a/include/Views/ViewStdMap.h +++ b/include/Views/ViewStdMap.h @@ -12,7 +12,7 @@ namespace S2Plugin { Q_OBJECT public: - ViewStdMap(const std::string& keytypeName, const std::string& valuetypeName, uintptr_t address, QWidget* parent = nullptr); + ViewStdMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName, QWidget* parent = nullptr); protected: void reloadContainer() override; diff --git a/include/Views/ViewStdUnorderedMap.h b/include/Views/ViewStdUnorderedMap.h new file mode 100644 index 0000000..7731a4e --- /dev/null +++ b/include/Views/ViewStdUnorderedMap.h @@ -0,0 +1,27 @@ +#pragma once + +#include "QtHelpers/AbstractContainerView.h" +#include +#include +#include + +namespace S2Plugin +{ + + class ViewStdUnorderedMap : public AbstractContainerView + { + Q_OBJECT + public: + ViewStdUnorderedMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName, QWidget* parent = nullptr); + + protected: + void reloadContainer() override; + + private: + std::string mMapKeyType; + std::string mMapValueType; + uintptr_t mMapAddress; + size_t mMapKeyTypeSize; + uint8_t mMapKeyAlignment; + }; +} // namespace S2Plugin diff --git a/include/Views/ViewStdVector.h b/include/Views/ViewStdVector.h index 6c27176..8d00aec 100644 --- a/include/Views/ViewStdVector.h +++ b/include/Views/ViewStdVector.h @@ -11,7 +11,7 @@ namespace S2Plugin { Q_OBJECT public: - ViewStdVector(const std::string& vectorType, uintptr_t vectoraddr, QWidget* parent = nullptr); + ViewStdVector(uintptr_t vectoraddr, const std::string& vectorType, QWidget* parent = nullptr); protected: void reloadContainer() override; diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index c038bf0..cd577fe 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -23,6 +23,7 @@ namespace S2Plugin void showState(uintptr_t statePtr); void showStdVector(uintptr_t address, const std::string& typeName); void showStdMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName); + void showStdUnorderedMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName); void showVirtualFunctions(uintptr_t address, const std::string& typeName); void showJournalPage(uintptr_t address); void showLevelGen(uintptr_t address); diff --git a/include/Views/ViewVirtualFunctions.h b/include/Views/ViewVirtualFunctions.h index 79d2f69..438bd94 100644 --- a/include/Views/ViewVirtualFunctions.h +++ b/include/Views/ViewVirtualFunctions.h @@ -13,7 +13,7 @@ namespace S2Plugin { Q_OBJECT public: - ViewVirtualFunctions(const std::string& typeName, uintptr_t address, QWidget* parent = nullptr); + ViewVirtualFunctions(uintptr_t address, const std::string& typeName, QWidget* parent = nullptr); protected: QSize sizeHint() const override; diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 1f49b87..6448204 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -786,7 +786,7 @@ { "field": "sound_killed_by_other", "type": "Dword" }, { "field": "field_a8", "type": "Float" }, { "field": "field_AC", "type": "Dword" }, - { "field": "animations", "type": "UnorderedMap" }, + { "field": "animations", "type": "StdUnorderedMap", "keytype": "UnsignedByte", "valuetype": "Animation" }, { "field": "attachOffsetX", "type": "Float" }, { "field": "attachOffsetY", "type": "Float" }, { "field": "init", "type": "UnsignedByte" }, @@ -795,6 +795,13 @@ { "field": "field_1b", "type": "UnsignedByte" }, { "field": "field_1c", "type": "Dword" } ], + "Animation": [ + { "field": "texture", "type": "Dword" }, + { "field": "count", "type": "Dword" }, + { "field": "interval", "type": "Dword" }, + { "field": "key", "type": "UnsignedByte" }, + { "field": "repeat", "type": "State8", "states": { "0": "NoRepeat", "1": "Linear", "2": "BackAndForth" } } + ], "EmittedParticlesInfo": [ { "field": "particle_count", "type": "UnsignedDword" }, { "field": "unknown2", "type": "UnsignedDword" }, @@ -2851,7 +2858,7 @@ { "field": "item_equipped_badge", "type": "TextureRenderingInfo" }, { "field": "item_off_gray_overlay", "type": "TextureRenderingInfo" }, { "field": "esc_woodpanel", "type": "TextureRenderingInfo" }, - { "field": "items_to_gray_out", "type": "UnorderedMap" }, + { "field": "items_to_gray_out", "type": "StdUnorderedMap", "keytype": "UnsignedDword", "valuetype": "Float" }, { "field": "unknown33", "type": "UnsignedDword" }, { "field": "center_panels_horizontal_slide_position", "type": "Float" }, { "field": "esc_panel_slide_timer", "type": "Float" }, @@ -5175,7 +5182,7 @@ { "field": "players_turn_scroll_handle", "type": "TextureRenderingInfo" }, { "field": "grid_player_icon", "type": "TextureRenderingInfo" }, { "field": "unknown30", "type": "Float" }, - { "field": "stages_to_gray_out", "type": "UnorderedMap" }, + { "field": "stages_to_gray_out", "type": "StdUnorderedMap", "keytype": "UnsignedDword", "valuetype": "Float" }, { "field": "unknown47", "type": "UnsignedDword" }, { "field": "unknown48", "type": "UnsignedDword" }, { "field": "unknown49", "type": "UnsignedByte" }, @@ -6395,28 +6402,96 @@ { "field": "machine_rewardroom_chance", "type": "UnsignedDword" }, { "field": "max_liquid_particles", "type": "UnsignedDword" }, { "field": "flagged_liquid_rooms", "type": "UnsignedDword" }, - { "field": "unknown_config", "type": "UnsignedDword" }, + { "field": "unknown_config", "type": "UnsignedDword", "comment": "probably padding" }, { "field": "short_tile_codes", - "type": "UnorderedMap", + "type": "StdUnorderedMap", + "keytype": "UnsignedByte", + "valuetype": "ShortTileCodeDef", "comment": "unordered_map" }, { "field": "tile_codes", - "type": "UnorderedMap", + "type": "StdUnorderedMap", + "keytype": "StdString", + "valuetype": "UnsignedDword", "comment": "unordered_map" }, { "field": "room_templates", - "type": "UnorderedMap", - "comment": "unordered_map" + "type": "StdUnorderedMap", + "keytype": "StdString", + "valuetype": "UnsignedWord", + "comment": "unordered_map" }, { "field": "room_template_datas", - "type": "UnorderedMap", - "comment": "unordered_map" + }, + { "field": "unknown1", "type": "Array", "length": 429, "arraytype": "UnsignedDword" }, + { "field": "padding", "type": "UnsignedDword" }, + { "field": "set_room_datas", "type": "Array", "length": 120, "arraytype": "RoomTemplateData" }, + { + "field": "monster_chances", + "type": "StdUnorderedMap", + "keytype": "StdString", + "valuetype": "UnsignedDword", + "comment": "unordered_map" + }, + { + "field": "level_monster_chances", + "type": "StdUnorderedMap", + "keytype": "UnsignedDword", + "valuetype": "LevelChanceDef", + "comment": "unordered_map" + }, + { + "field": "trap_chances", + "type": "StdUnorderedMap", + "keytype": "StdString", + "valuetype": "UnsignedDword", + "comment": "unordered_map" + }, + { + "field": "level_trap_chances", + "type": "StdUnorderedMap", + "keytype": "UnsignedDword", + "valuetype": "LevelChanceDef", + "comment": "unordered_map" } ], + "ShortTileCodeDef": [ + { "field": "tilecode", "type": "UnsignedDword" }, + { "field": "chance", "type": "UnsignedByte" }, + { "field": "padding1", "type": "UnsignedByte" }, + { "field": "padding2", "type": "UnsignedByte" }, + { "field": "padding3", "type": "UnsignedByte" }, + { "field": "alt_tilecode", "type": "UnsignedDword" } + ], + "RoomTemplateData": [ + { "field": "datas", "type": "StdVector", "valuetype": "RoomData" } + ], + "LevelChanceDef": [ + { "field": "datas", "type": "StdVector", "valuetype": "UnsignedDword" } + ], + "RoomData": [ + { "field": "flags", "type": "Flags8", + "flags": + { + "4": "flipped", + "5": "dual" + } + }, + { "field": "room_width", "type": "UnsignedByte" }, + { "field": "room_height", "type": "UnsignedByte" }, + { "field": "padding1", "type": "UnsignedByte" }, + { "field": "padding2", "type": "UnsignedDword" }, + { "field": "room_data", "type": "DataPointer" } + + ], "SpecialLevelGeneration": [ { "field": "__vftable", @@ -6803,7 +6878,7 @@ "comment": "type obviously may vary" } ], - "UnorderedMap": [ + "StdUnorderedMap": [ { "field": "max_load_factor", "type": "Float" }, { "field": "-", "type": "Skip", "offset": 4 }, { @@ -8210,7 +8285,15 @@ "field": "unknown27", "type": "StdMap", "comment": "added map for now, the size seam to always be 0" - } + }, + { "field": "unknown28", "type": "UnsignedQword" }, + { "field": "unknown29", "type": "UnsignedQword" }, + { "field": "unknown30", "type": "UnsignedQword" }, + { "field": "unknown31", "type": "StdVector", "valuetype": "UnsignedQword" }, + { "field": "unknown34", "type": "StdMap", "keytype": "UnsignedDword", "valuetype": "UnsignedByte" }, + { "field": "unknown35", "type": "UnsignedQword" }, + { "field": "unknown36", "type": "DataPointer" }, + { "field": "unknown37", "type": "StdMap" } ], "MovableBehavior": [ { diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 47f1f80..161080f 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -114,6 +114,7 @@ namespace S2Plugin {MemoryFieldType::StdWstring, "StdWstring", "std::wstring", "StdWstring", 32, false}, {MemoryFieldType::OldStdList, "OldStdList", "std::pair", "OldStdList", 16, false}, // can't use std::list representation since the standard was changed {MemoryFieldType::StdList, "StdList", "std::list", "StdList", 16, false}, + {MemoryFieldType::StdUnorderedMap, "StdUnorderedMap", "std::unordered_map", "StdUnorderedMap", 64, false}, // Game Main structs {MemoryFieldType::GameManager, "GameManager", "", "GameManager", 0, false}, {MemoryFieldType::State, "State", "", "State", 0, false}, @@ -323,6 +324,43 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman dprintf("no keytype specified for StdSet (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); } } + else if (fieldTypeStr == "StdUnorderedMap") + { + memField.type = MemoryFieldType::StdUnorderedMap; + if (field.contains("keytype")) + { + memField.firstParameterType = field["keytype"].get(); + } + else + { + memField.firstParameterType = "UnsignedQword"; + dprintf("no keytype specified for StdUnorderedMap (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); + } + if (field.contains("valuetype")) + { + memField.secondParameterType = field["valuetype"].get(); + } + else + { + memField.secondParameterType = "UnsignedQword"; + dprintf("no valuetype specified for StdUnorderedMap (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); + } + } + else if (fieldTypeStr == "StdUnorderedSet") + { + memField.type = MemoryFieldType::StdUnorderedMap; + if (field.contains("keytype")) + { + memField.firstParameterType = field["keytype"].get(); + memField.secondParameterType = ""; + } + else + { + memField.firstParameterType = "UnsignedQword"; + memField.secondParameterType = ""; + dprintf("no keytype specified for StdUnorderedSet (%s.%s)\n", struct_name.c_str(), memField.name.c_str()); + } + } switch (memField.type) { case MemoryFieldType::Skip: @@ -766,7 +804,7 @@ std::vector S2Plugin::Configuration::virtualFunctions } } -int S2Plugin::Configuration::getAlingment(const std::string& typeName) const +uint8_t S2Plugin::Configuration::getAlingment(const std::string& typeName) const { if (isPermanentPointer(typeName)) { @@ -777,68 +815,99 @@ int S2Plugin::Configuration::getAlingment(const std::string& typeName) const if (isPointerType(type)) return sizeof(uintptr_t); - switch (type) - { - case MemoryFieldType::Skip: - { - dprintf("cannot determinate alignment of (Skip) type!\n"); - return sizeof(uintptr_t); - } - case MemoryFieldType::Byte: - case MemoryFieldType::UnsignedByte: - case MemoryFieldType::Bool: - case MemoryFieldType::Flags8: - case MemoryFieldType::State8: - case MemoryFieldType::CharacterDBID: - case MemoryFieldType::UTF8StringFixedSize: - return sizeof(char); - case MemoryFieldType::Word: - case MemoryFieldType::UnsignedWord: - case MemoryFieldType::State16: - case MemoryFieldType::Flags16: - case MemoryFieldType::UTF16StringFixedSize: - case MemoryFieldType::UTF16Char: - return sizeof(int16_t); - case MemoryFieldType::Dword: - case MemoryFieldType::UnsignedDword: - case MemoryFieldType::Float: - case MemoryFieldType::Flags32: - case MemoryFieldType::State32: - case MemoryFieldType::EntityDBID: - case MemoryFieldType::ParticleDBID: - case MemoryFieldType::EntityUID: - case MemoryFieldType::TextureDBID: - case MemoryFieldType::StringsTableID: - case MemoryFieldType::IPv4Address: - case MemoryFieldType::CharacterDB: // biggest type is 4 - return sizeof(int32_t); - - case MemoryFieldType::Online: - case MemoryFieldType::TextureDB: - case MemoryFieldType::ParticleDB: - case MemoryFieldType::EntityDB: - case MemoryFieldType::LevelGen: - case MemoryFieldType::GameManager: - case MemoryFieldType::State: - case MemoryFieldType::SaveGame: - case MemoryFieldType::StdVector: - case MemoryFieldType::StdMap: - case MemoryFieldType::Qword: - case MemoryFieldType::UnsignedQword: - case MemoryFieldType::Double: - case MemoryFieldType::OldStdList: - case MemoryFieldType::StdList: - case MemoryFieldType::EntityList: - return sizeof(uintptr_t); - } + return getAlingment(type); } auto itr = mAlignments.find(typeName); if (itr != mAlignments.end()) return itr->second; + uint8_t alignment = 0; + for (auto& field : typeFieldsOfDefaultStruct(typeName)) + { + alignment = std::max(alignment, getAlingment(field)); + if (alignment == 8) + break; + } + if (alignment != 0) + return alignment; + dprintf("alignment not found for (%s)\n", typeName.c_str()); return sizeof(uintptr_t); } +uint8_t S2Plugin::Configuration::getAlingment(const MemoryField& field) const +{ + if (field.isPointer) + return sizeof(uintptr_t); + + switch (field.type) + { + case MemoryFieldType::Array: + case MemoryFieldType::Matrix: + return getAlingment(field.firstParameterType); + case MemoryFieldType::DefaultStructType: + return getAlingment(field.jsonName); + default: + return getAlingment(field.type); + } +} +uint8_t S2Plugin::Configuration::getAlingment(MemoryFieldType type) const +{ + switch (type) + { + case MemoryFieldType::Skip: + { + dprintf("cannot determinate alignment of (Skip) type!\n"); + return sizeof(uintptr_t); + } + case MemoryFieldType::Byte: + case MemoryFieldType::UnsignedByte: + case MemoryFieldType::Bool: + case MemoryFieldType::Flags8: + case MemoryFieldType::State8: + case MemoryFieldType::CharacterDBID: + case MemoryFieldType::UTF8StringFixedSize: + return sizeof(char); + case MemoryFieldType::Word: + case MemoryFieldType::UnsignedWord: + case MemoryFieldType::State16: + case MemoryFieldType::Flags16: + case MemoryFieldType::UTF16StringFixedSize: + case MemoryFieldType::UTF16Char: + return sizeof(int16_t); + case MemoryFieldType::Dword: + case MemoryFieldType::UnsignedDword: + case MemoryFieldType::Float: + case MemoryFieldType::Flags32: + case MemoryFieldType::State32: + case MemoryFieldType::EntityDBID: + case MemoryFieldType::ParticleDBID: + case MemoryFieldType::EntityUID: + case MemoryFieldType::TextureDBID: + case MemoryFieldType::StringsTableID: + case MemoryFieldType::IPv4Address: + case MemoryFieldType::CharacterDB: // biggest type is 4 + return sizeof(int32_t); + + case MemoryFieldType::Online: + case MemoryFieldType::TextureDB: + case MemoryFieldType::ParticleDB: + case MemoryFieldType::EntityDB: + case MemoryFieldType::LevelGen: + case MemoryFieldType::GameManager: + case MemoryFieldType::State: + case MemoryFieldType::SaveGame: + case MemoryFieldType::StdVector: + case MemoryFieldType::StdMap: + case MemoryFieldType::Qword: + case MemoryFieldType::UnsignedQword: + case MemoryFieldType::Double: + case MemoryFieldType::OldStdList: + case MemoryFieldType::StdList: + case MemoryFieldType::EntityList: + case MemoryFieldType::StdUnorderedMap: + return sizeof(uintptr_t); + } +} size_t S2Plugin::Configuration::getTypeSize(const std::string& typeName, bool entitySubclass) { diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index f4720d4..db7be40 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -8,6 +8,7 @@ #include "Data/State.h" #include "Data/StdList.h" #include "Data/StdString.h" +#include "Data/StdUnorderedMap.h" #include "Data/StringsTable.h" #include "Data/TextureDB.h" #include "QtHelpers/DialogEditSimpleValue.h" @@ -156,6 +157,8 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& typeName += QString::fromStdString(field.jsonName); else if (field.type == MemoryFieldType::StdMap && field.secondParameterType.empty()) // exception typeName += "StdSet"; + else if (field.type == MemoryFieldType::StdUnorderedMap && field.secondParameterType.empty()) // exception + typeName += "StdUnorderedSet"; else if (auto str = Configuration::getTypeDisplayName(field.type); !str.empty()) typeName += QString::fromUtf8(str.data(), static_cast(str.size())); else @@ -293,6 +296,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& break; } + case MemoryFieldType::StdUnorderedMap: case MemoryFieldType::StdMap: { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); @@ -1961,6 +1965,80 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::StdUnorderedMap: + { + std::optional value; + if (valueMemoryOffset == 0) + { + itemValue->setData({}, Qt::DisplayRole); + if (!isPointer) + itemValueHex->setData({}, Qt::DisplayRole); + + itemValue->setData({}, gsRoleRawValue); + itemField->setBackground(Qt::transparent); + } + else + { + value = {0}; + value->resize(gsStdUnorderedMapSize); + Script::Memory::Read(valueMemoryOffset, value->data(), gsStdUnorderedMapSize, nullptr); + auto dataOld = itemValue->data(gsRoleRawValue); + auto valueOld = dataOld.value(); + if (!dataOld.isValid() || value.value() != valueOld) + { + itemField->setBackground(highlightColor); + itemValue->setData(QVariant::fromValue(value.value()), gsRoleRawValue); + + if (StdUnorderedMap{valueMemoryOffset, 0, 0}.empty()) + itemValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemValue->setData("Show contents", Qt::DisplayRole); + } + else if (!isPointer) + itemField->setBackground(Qt::transparent); + } + + if (comparisonActive) + { + std::optional comparisonValue; + if (valueComparisonMemoryOffset == 0) + { + itemComparisonValue->setData({}, Qt::DisplayRole); + if (!isPointer) + itemComparisonValueHex->setData({}, Qt::DisplayRole); + + itemComparisonValue->setData({}, gsRoleRawValue); + } + else + { + comparisonValue = {0}; + comparisonValue->resize(gsStdUnorderedMapSize); + Script::Memory::Read(valueComparisonMemoryOffset, comparisonValue->data(), gsStdUnorderedMapSize, nullptr); + auto dataOld = itemComparisonValue->data(gsRoleRawValue); + auto valueOld = dataOld.value(); + if (!dataOld.isValid() || comparisonValue.value() != valueOld) + { + itemComparisonValue->setData(QVariant::fromValue(comparisonValue.value()), gsRoleRawValue); + + if (StdUnorderedMap{valueComparisonMemoryOffset, 0, 0}.empty()) + itemComparisonValue->setData("Show contents (empty)", Qt::DisplayRole); + else + itemComparisonValue->setData("Show contents", Qt::DisplayRole); + } + } + itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + if (isPointer == false) + itemComparisonValueHex->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + } + if (shouldUpdateChildren) + { + std::optional addr = pointerUpdate ? valueMemoryOffset : (isPointer ? std::nullopt : newAddr); + std::optional comparisonAddr = comparisonPointerUpdate ? valueComparisonMemoryOffset : (isPointer ? std::nullopt : newAddrComparison); + for (uint8_t x = 0; x < itemField->rowCount(); ++x) + updateRow(x, addr, comparisonAddr, itemField); + } + break; + } case MemoryFieldType::StdList: case MemoryFieldType::OldStdList: { @@ -1971,19 +2049,19 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (!isPointer) itemValueHex->setData({}, Qt::DisplayRole); - itemValue->setData({}, S2Plugin::gsRoleRawValue); + itemValue->setData({}, gsRoleRawValue); itemField->setBackground(Qt::transparent); } else { value = {0, 0}; Script::Memory::Read(valueMemoryOffset, &value.value(), 2 * sizeof(uintptr_t), nullptr); - auto dataOld = itemValue->data(S2Plugin::gsRoleRawValue); + auto dataOld = itemValue->data(gsRoleRawValue); auto valueOld = dataOld.value>(); if (!dataOld.isValid() || value.value() != valueOld) { itemField->setBackground(highlightColor); - itemValue->setData(QVariant::fromValue(value.value()), S2Plugin::gsRoleRawValue); + itemValue->setData(QVariant::fromValue(value.value()), gsRoleRawValue); bool empty = (fieldType == MemoryFieldType::OldStdList && OldStdList{valueMemoryOffset}.empty()) || (fieldType == MemoryFieldType::StdList && StdList{valueMemoryOffset}.empty()); @@ -2005,17 +2083,17 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional if (!isPointer) itemComparisonValueHex->setData({}, Qt::DisplayRole); - itemComparisonValue->setData({}, S2Plugin::gsRoleRawValue); + itemComparisonValue->setData({}, gsRoleRawValue); } else { comparisonValue = {0, 0}; Script::Memory::Read(valueComparisonMemoryOffset, &comparisonValue.value(), 2 * sizeof(uintptr_t), nullptr); - auto dataOld = itemComparisonValue->data(S2Plugin::gsRoleRawValue); + auto dataOld = itemComparisonValue->data(gsRoleRawValue); auto valueOld = dataOld.value>(); if (!dataOld.isValid() || comparisonValue.value() != valueOld) { - itemComparisonValue->setData(QVariant::fromValue(comparisonValue.value()), S2Plugin::gsRoleRawValue); + itemComparisonValue->setData(QVariant::fromValue(comparisonValue.value()), gsRoleRawValue); bool empty = (fieldType == MemoryFieldType::OldStdList && OldStdList{valueComparisonMemoryOffset}.empty()) || (fieldType == MemoryFieldType::StdList && StdList{valueComparisonMemoryOffset}.empty()); @@ -2594,6 +2672,17 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } break; } + case MemoryFieldType::StdUnorderedMap: + { + auto address = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (address != 0) + { + auto keyTypeName = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerFirstParameterType)); + auto ValueTypeName = qvariant_cast(getDataFrom(index, gsColField, gsRoleStdContainerSecondParameterType)); + getToolbar()->showStdUnorderedMap(address, keyTypeName, ValueTypeName); + } + break; + } } emit memoryFieldValueUpdated(index.row(), clickedItem->parent()); } diff --git a/src/Views/ViewStdMap.cpp b/src/Views/ViewStdMap.cpp index 704031c..2f442d7 100644 --- a/src/Views/ViewStdMap.cpp +++ b/src/Views/ViewStdMap.cpp @@ -6,7 +6,7 @@ #include "pluginmain.h" #include -S2Plugin::ViewStdMap::ViewStdMap(const std::string& keytypeName, const std::string& valuetypeName, uintptr_t address, QWidget* parent) +S2Plugin::ViewStdMap::ViewStdMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName, QWidget* parent) : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mMapAddress(address), AbstractContainerView(parent) { auto config = Configuration::get(); diff --git a/src/Views/ViewStdUnorderedMap.cpp b/src/Views/ViewStdUnorderedMap.cpp new file mode 100644 index 0000000..7c2ca97 --- /dev/null +++ b/src/Views/ViewStdUnorderedMap.cpp @@ -0,0 +1,113 @@ +#include "Views/ViewStdUnorderedMap.h" + +#include "Configuration.h" +#include "Data/StdUnorderedMap.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "pluginmain.h" +#include + +S2Plugin::ViewStdUnorderedMap::ViewStdUnorderedMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName, QWidget* parent) + : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mMapAddress(address), AbstractContainerView(parent) +{ + auto config = Configuration::get(); + mMapKeyTypeSize = config->getTypeSize(keytypeName); + mMapKeyAlignment = std::max(config->getAlingment(keytypeName), config->getAlingment(valuetypeName)); + + if (mMapValueType.empty()) + setWindowTitle(QString("std::unordered_set<%1>").arg(QString::fromStdString(keytypeName))); + else + setWindowTitle(QString("std::unordered_map<%1, %2>").arg(QString::fromStdString(keytypeName), QString::fromStdString(valuetypeName))); + + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColComment); + reloadContainer(); +} + +void S2Plugin::ViewStdUnorderedMap::reloadContainer() +{ + if (!Script::Memory::IsValidPtr(Script::Memory::ReadQword(mMapAddress + sizeof(uintptr_t)))) + return; + + auto config = Configuration::get(); + mMainTreeView->clear(); + + MemoryField key_field; + key_field.name = "key"; + if (config->isPermanentPointer(mMapKeyType)) + { + key_field.type = MemoryFieldType::DefaultStructType; + key_field.jsonName = mMapKeyType; + key_field.isPointer = true; + } + else if (config->isJsonStruct(mMapKeyType)) + { + key_field.type = MemoryFieldType::DefaultStructType; + key_field.jsonName = mMapKeyType; + } + else if (auto type = config->getBuiltInType(mMapKeyType); type != MemoryFieldType::None) + { + key_field.type = type; + if (Configuration::isPointerType(type)) + key_field.isPointer = true; + } + else + { + dprintf("unknown type in ViewStdMap::refreshMapContents() (%s)\n", mMapKeyType.c_str()); + return; + } + + MemoryField value_field; + if (!mMapValueType.empty()) // if not StdSet + { + value_field.name = "value"; + if (config->isPermanentPointer(mMapValueType)) + { + value_field.type = MemoryFieldType::DefaultStructType; + value_field.jsonName = mMapValueType; + value_field.isPointer = true; + } + else if (config->isJsonStruct(mMapValueType)) + { + value_field.type = MemoryFieldType::DefaultStructType; + value_field.jsonName = mMapValueType; + } + else if (auto type = config->getBuiltInType(mMapValueType); type != MemoryFieldType::None) + { + value_field.type = type; + if (Configuration::isPointerType(type)) + value_field.isPointer = true; + } + else + { + dprintf("unknown type in ViewStdMap::refreshMapContents() (%s)\n", mMapValueType.c_str()); + return; + } + } + + StdUnorderedMap the_map{mMapAddress, mMapKeyTypeSize, mMapKeyAlignment}; + auto _end = the_map.end(); + auto _cur = the_map.begin(); + MemoryField parent_field; + parent_field.type = MemoryFieldType::Dummy; + for (int x = 0; _cur != _end && x < 300; ++x, ++_cur) + { + if (mMapValueType.empty()) // StdSet + { + key_field.name = "key_" + std::to_string(x); + mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0); + } + else // StdMap + { + parent_field.name = "obj_" + std::to_string(x); + QStandardItem* parent = mMainTreeView->addMemoryField(parent_field, parent_field.name, 0, 0); + mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0, 0, parent); + mMainTreeView->addMemoryField(value_field, value_field.name, _cur.value_ptr(), 0, 0, parent); + } + } + mMainTreeView->updateTableHeader(); + mMainTreeView->setColumnWidth(gsColField, 145); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 300); + mMainTreeView->updateTree(0, 0, true); +} diff --git a/src/Views/ViewStdVector.cpp b/src/Views/ViewStdVector.cpp index fc3ea62..e0c55a7 100644 --- a/src/Views/ViewStdVector.cpp +++ b/src/Views/ViewStdVector.cpp @@ -5,7 +5,7 @@ #include "pluginmain.h" #include -S2Plugin::ViewStdVector::ViewStdVector(const std::string& vectorType, uintptr_t vectorAddr, QWidget* parent) : mVectorType(vectorType), mVectorAddress(vectorAddr), AbstractContainerView(parent) +S2Plugin::ViewStdVector::ViewStdVector(uintptr_t vectoraddr, const std::string& vectorType, QWidget* parent) : mVectorType(vectorType), mVectorAddress(vectoraddr), AbstractContainerView(parent) { mVectorTypeSize = Configuration::get()->getTypeSize(mVectorType); diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index d652720..a228e37 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -13,6 +13,7 @@ #include "Views/ViewParticleDB.h" #include "Views/ViewStdList.h" #include "Views/ViewStdMap.h" +#include "Views/ViewStdUnorderedMap.h" #include "Views/ViewStdVector.h" #include "Views/ViewStringsTable.h" #include "Views/ViewStruct.h" @@ -132,7 +133,7 @@ S2Plugin::ViewToolbar::ViewToolbar(QMdiArea* mdiArea, QWidget* parent) : QDockWi void S2Plugin::ViewToolbar::showVirtualFunctions(uintptr_t address, const std::string& typeName) { - auto w = new ViewVirtualFunctions(typeName, address); + auto w = new ViewVirtualFunctions(address, typeName); auto win = mMDIArea->addSubWindow(w); win->setVisible(true); win->setAttribute(Qt::WA_DeleteOnClose); @@ -140,7 +141,7 @@ void S2Plugin::ViewToolbar::showVirtualFunctions(uintptr_t address, const std::s void S2Plugin::ViewToolbar::showStdVector(uintptr_t address, const std::string& typeName) { - auto w = new ViewStdVector(typeName, address); + auto w = new ViewStdVector(address, typeName); auto win = mMDIArea->addSubWindow(w); win->setVisible(true); win->setAttribute(Qt::WA_DeleteOnClose); @@ -148,7 +149,15 @@ void S2Plugin::ViewToolbar::showStdVector(uintptr_t address, const std::string& void S2Plugin::ViewToolbar::showStdMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName) { - auto w = new ViewStdMap(keytypeName, valuetypeName, address); + auto w = new ViewStdMap(address, keytypeName, valuetypeName); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + +void S2Plugin::ViewToolbar::showStdUnorderedMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName) +{ + auto w = new ViewStdUnorderedMap(address, keytypeName, valuetypeName); auto win = mMDIArea->addSubWindow(w); win->setVisible(true); win->setAttribute(Qt::WA_DeleteOnClose); diff --git a/src/Views/ViewVirtualFunctions.cpp b/src/Views/ViewVirtualFunctions.cpp index 0328c90..d5d0269 100644 --- a/src/Views/ViewVirtualFunctions.cpp +++ b/src/Views/ViewVirtualFunctions.cpp @@ -9,7 +9,7 @@ #include #include -S2Plugin::ViewVirtualFunctions::ViewVirtualFunctions(const std::string& typeName, uintptr_t address, QWidget* parent) : QWidget(parent), mMemoryAddress(address) +S2Plugin::ViewVirtualFunctions::ViewVirtualFunctions(uintptr_t address, const std::string& typeName, QWidget* parent) : QWidget(parent), mMemoryAddress(address) { setWindowIcon(getCavemanIcon()); setWindowTitle(QString("Virtual Functions of %1").arg(QString::fromStdString(typeName))); From 024530f80c847473b817f3591fb92b8c804a735f Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 9 Jun 2024 15:36:03 +0200 Subject: [PATCH 12/20] improvements to the map --- include/Configuration.h | 1 - include/Data/StdMap.h | 224 ++++++++++++++-------------- include/Views/ViewStdUnorderedMap.h | 2 +- src/Configuration.cpp | 9 ++ src/Views/ViewStdMap.cpp | 49 +----- src/Views/ViewStdUnorderedMap.cpp | 61 ++------ 6 files changed, 138 insertions(+), 208 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index c336341..d3d0381 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -227,7 +227,6 @@ namespace S2Plugin static std::string_view getTypeDisplayName(MemoryFieldType type); static size_t getBuiltInTypeSize(MemoryFieldType type); static bool isPointerType(MemoryFieldType type); - // unknown name will be created as DefaultStructType MemoryField nameToMemoryField(const std::string& typeName) const; uintptr_t offsetForField(const std::vector& fields, std::string_view fieldUID, uintptr_t base_addr = 0) const; diff --git a/include/Data/StdMap.h b/include/Data/StdMap.h index 96853f6..9697e83 100644 --- a/include/Data/StdMap.h +++ b/include/Data/StdMap.h @@ -7,93 +7,84 @@ namespace S2Plugin { // For the proper use you need to use template to define the types - // otherwise the functions ::find ::at ::contains ::node::key ::node::value, won't work properly - // when not using template, you need to define the size of key and value with the correct constructor - // this can also be used as std::set, just set the value size to 0 + // otherwise the functions ::find ::at ::contains ::node::key ::node::value, auto for loop, won't work properly + // when not using template, you need to define the size of key and alignments with the correct constructor + // this can also be used as std::set, just set the value alignment to 0 // it's not the safes implementation but the one that was needed + namespace + { + struct _EmptyType + { + }; + } // namespace - template + template struct StdMap { // only for the template - StdMap(uintptr_t addr) : address(addr) + template + StdMap(uintptr_t addr, std::enable_if_t || !std::is_same_v, int> = 0) { - keytype_size = sizeof(Key); - valuetype_size = sizeof(Value); - set_offsets(); + uintptr_t data[2]; + Script::Memory::Read(addr, &data, sizeof(data), nullptr); + head = Node{data[0]}; + mSize = data[1]; + head.set_offsets(); }; // value size only needed for value() function - StdMap(uintptr_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize) : address(addr) + StdMap(uintptr_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize) { - keytype_size = keySize; - valuetype_size = sizeof(Value); - set_offsets(keyAlignment, valueAlignment); - }; - StdMap(uintptr_t addr, uint8_t keyAlignment, uint8_t valueAlignment, size_t keySize, size_t valueSize) : address(addr) - { - keytype_size = keySize; - valuetype_size = valueSize; - set_offsets(keyAlignment, valueAlignment); + uintptr_t data[2]; + Script::Memory::Read(addr, &data, sizeof(data), nullptr); + head = Node{data[0]}; + mSize = data[1]; + head.set_offsets(keySize, keyAlignment, valueAlignment); }; struct Node { - Node(size_t addr, const StdMap* _map) : node_ptr(addr), parent_map(_map){}; - Node(Node& t) : node_ptr(t.node_ptr), parent_map(t.parent_map){}; + Node(const Node& t) : node_ptr(t.node_ptr), key_offset(t.key_offset), value_offset(t.value_offset){}; Key key() const { - auto key_address = key_ptr(); - // would probably be better with Read function but - // probably doesn't matter as for large structures you will just grab the address most of the time - switch (parent_map->keytype_size) - { - case size_byte: - return (Key)Script::Memory::ReadByte(key_address); - case size_word: - return (Key)Script::Memory::ReadWord(key_address); - case size_dword: - return (Key)Script::Memory::ReadDword(key_address); - } - return (Key)Script::Memory::ReadQword(key_address); + Key tmp{}; + Script::Memory::Read(key_ptr(), &tmp, sizeof(Key), nullptr); + return tmp; } Value value() const { - auto value_address = value_ptr(); - // same as key() - switch (parent_map->valuetype_size) - { - case size_byte: - return (Value)Script::Memory::ReadByte(value_address); - case size_word: - return (Value)Script::Memory::ReadWord(value_address); - case size_dword: - return (Value)Script::Memory::ReadDword(value_address); - } - return (Value)Script::Memory::ReadQword(value_address); + Value tmp{}; + Script::Memory::Read(value_ptr(), &tmp, sizeof(Value), nullptr); + return tmp; } uintptr_t key_ptr() const { - return node_ptr + parent_map->key_offset; + return node_ptr + key_offset; } uintptr_t value_ptr() const { - return node_ptr + parent_map->value_offset; + return node_ptr + value_offset; } Node left() const { auto left_addr = Script::Memory::ReadQword(node_ptr); - return Node{left_addr, parent_map}; + Node copy = *this; + copy.node_ptr = left_addr; + return copy; } Node parent() const { auto parent_addr = Script::Memory::ReadQword(node_ptr + 0x8); - return Node{parent_addr, parent_map}; + Node copy = *this; + copy.node_ptr = parent_addr; + return copy; } Node right() const { auto right_addr = Script::Memory::ReadQword(node_ptr + 0x10); - return Node{right_addr, parent_map}; + Node copy = *this; + copy.node_ptr = right_addr; + return copy; } bool color() const { @@ -156,24 +147,79 @@ namespace S2Plugin } // not doing the -- operator for now as it's not really needed - bool operator==(Node other) const + bool operator==(Node other) const noexcept { return other.node_ptr == node_ptr; } - bool operator!=(Node other) const + bool operator!=(Node other) const noexcept { return other.node_ptr != node_ptr; } private: - uintptr_t node_ptr; - // need reference to the map object so we can get offsets and alignments - const StdMap* parent_map; + Node() = default; + Node(size_t addr) : node_ptr(addr){}; + void set_offsets(size_t keytype_size = sizeof(Key), uint8_t key_alignment = alignof(Key), uint8_t value_alignment = alignof(Value)) noexcept + { + // key and value in map are treated as std::pair + // we need to figure out if it's placed right after the bucket flags + // or if there is a padding added for alignment + // the issue is, if key or value are a structs, we need to know their alignments, not just their size + + uint8_t alignment = std::max(key_alignment, value_alignment); + + switch (alignment) + { + case 0: + case 1: + case 2: + key_offset = 0x1A; // 3 pointers and 2 bool field + break; + case 3: + case 4: + key_offset = 0x1C; + break; + case 5: + case 6: + case 7: + case 8: + default: + key_offset = 0x20; + break; + } + size_t offset = key_offset + keytype_size; + // dealing with the padding between key and value + switch (value_alignment) + { + case 0: + case 1: + value_offset = offset; + break; + case 2: + value_offset = (offset + 1) & ~1; + break; + case 3: + case 4: + value_offset = (offset + 3) & ~3; + break; + case 5: + case 6: + case 7: + case 8: + default: + value_offset = (offset + 7) & ~7; + break; + } + } + uintptr_t node_ptr{0}; + size_t key_offset{0}; + size_t value_offset{0}; + friend struct StdMap; }; - size_t size() const + size_t size() const noexcept { - return Script::Memory::ReadQword(address + 0x8); + return mSize; } size_t at(Key v) const { @@ -186,12 +232,11 @@ namespace S2Plugin } Node begin() const { - return end().left(); + return head.left(); } - Node end() const + Node end() const noexcept { - Node a{Script::Memory::ReadQword(address), this}; - return a; + return head; } Node find(Key k) const { @@ -231,61 +276,12 @@ namespace S2Plugin { return end().right(); } - void set_offsets(uint8_t key_alignment = alignof(Key), uint8_t value_alignment = alignof(Value)) + Node first() const { - // key and value in map are treated as std::pair - // we need to figure out if it's placed right after the bucket flags - // or if there is a padding added for aliment - // the issue is, if key or value are a structs, we need to know their alignments, not just their size - - uint8_t alignment = std::max(key_alignment, value_alignment); - - switch (alignment) - { - case 0: - case 1: - case 2: - key_offset = 0x1A; - break; - case 3: - case 4: - key_offset = 0x1C; - break; - case 5: - case 6: - case 7: - case 8: - key_offset = 0x20; - break; - } - size_t offset = key_offset + keytype_size; - // dealing with the padding between key and value - switch (value_alignment) - { - case 0: - case 1: - value_offset = offset; - break; - case 2: - value_offset = (offset + 1) & ~1; - break; - case 3: - case 4: - value_offset = (offset + 3) & ~3; - break; - case 5: - case 6: - case 7: - case 8: - value_offset = (offset + 7) & ~7; - break; - } + return end().left(); } - uintptr_t address; - size_t keytype_size; - size_t valuetype_size; - uint8_t key_offset; - size_t value_offset; + Node head; + size_t mSize; }; } // namespace S2Plugin diff --git a/include/Views/ViewStdUnorderedMap.h b/include/Views/ViewStdUnorderedMap.h index 7731a4e..f599887 100644 --- a/include/Views/ViewStdUnorderedMap.h +++ b/include/Views/ViewStdUnorderedMap.h @@ -22,6 +22,6 @@ namespace S2Plugin std::string mMapValueType; uintptr_t mMapAddress; size_t mMapKeyTypeSize; - uint8_t mMapKeyAlignment; + uint8_t mMapAlignment; }; } // namespace S2Plugin diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 161080f..24c04af 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -905,6 +905,7 @@ uint8_t S2Plugin::Configuration::getAlingment(MemoryFieldType type) const case MemoryFieldType::StdList: case MemoryFieldType::EntityList: case MemoryFieldType::StdUnorderedMap: + default: return sizeof(uintptr_t); } } @@ -1118,9 +1119,17 @@ bool S2Plugin::Configuration::isPointerType(MemoryFieldType type) S2Plugin::MemoryField S2Plugin::Configuration::nameToMemoryField(const std::string& name) const { MemoryField field; + if (name.empty()) + return field; + auto type = getBuiltInType(name); if (type == MemoryFieldType::None) { + if (!isJsonStruct(name)) + { + dprintf("unknown type name requested in nameToMemoryField(%s)", name.c_str()); + return field; + } field.type = MemoryFieldType::DefaultStructType; field.jsonName = name; field.isPointer = isPermanentPointer(name); diff --git a/src/Views/ViewStdMap.cpp b/src/Views/ViewStdMap.cpp index 2f442d7..94210d4 100644 --- a/src/Views/ViewStdMap.cpp +++ b/src/Views/ViewStdMap.cpp @@ -14,7 +14,7 @@ S2Plugin::ViewStdMap::ViewStdMap(uintptr_t address, const std::string& keytypeNa mMapValueTypeSize = config->getTypeSize(valuetypeName); mMapKeyAlignment = config->getAlingment(keytypeName); - mMapValueAlignment = config->getAlingment(valuetypeName); + mMapValueAlignment = mMapValueTypeSize == 0 ? 0 : config->getAlingment(valuetypeName); if (mMapValueTypeSize == 0) setWindowTitle(QString("std::set<%1>").arg(QString::fromStdString(keytypeName))); @@ -34,57 +34,18 @@ void S2Plugin::ViewStdMap::reloadContainer() auto config = Configuration::get(); mMainTreeView->clear(); - MemoryField key_field; + MemoryField key_field = config->nameToMemoryField(mMapKeyType); key_field.name = "key"; - if (config->isPermanentPointer(mMapKeyType)) - { - key_field.type = MemoryFieldType::DefaultStructType; - key_field.jsonName = mMapKeyType; - key_field.isPointer = true; - } - else if (config->isJsonStruct(mMapKeyType)) - { - key_field.type = MemoryFieldType::DefaultStructType; - key_field.jsonName = mMapKeyType; - } - else if (auto type = config->getBuiltInType(mMapKeyType); type != MemoryFieldType::None) - { - key_field.type = type; - if (Configuration::isPointerType(type)) - key_field.isPointer = true; - } - else - { - dprintf("unknown type in ViewStdMap::refreshMapContents() (%s)\n", mMapKeyType.c_str()); + if (key_field.type == MemoryFieldType::None) return; - } MemoryField value_field; if (mMapValueTypeSize != 0) // if not StdSet { + value_field = config->nameToMemoryField(mMapValueType); value_field.name = "value"; - if (config->isPermanentPointer(mMapValueType)) - { - value_field.type = MemoryFieldType::DefaultStructType; - value_field.jsonName = mMapValueType; - value_field.isPointer = true; - } - else if (config->isJsonStruct(mMapValueType)) - { - value_field.type = MemoryFieldType::DefaultStructType; - value_field.jsonName = mMapValueType; - } - else if (auto type = config->getBuiltInType(mMapValueType); type != MemoryFieldType::None) - { - value_field.type = type; - if (Configuration::isPointerType(type)) - value_field.isPointer = true; - } - else - { - dprintf("unknown type in ViewStdMap::refreshMapContents() (%s)\n", mMapValueType.c_str()); + if (value_field.type == MemoryFieldType::None) return; - } } auto _end = the_map.end(); diff --git a/src/Views/ViewStdUnorderedMap.cpp b/src/Views/ViewStdUnorderedMap.cpp index 7c2ca97..76cb004 100644 --- a/src/Views/ViewStdUnorderedMap.cpp +++ b/src/Views/ViewStdUnorderedMap.cpp @@ -10,13 +10,17 @@ S2Plugin::ViewStdUnorderedMap::ViewStdUnorderedMap(uintptr_t address, const std: : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mMapAddress(address), AbstractContainerView(parent) { auto config = Configuration::get(); - mMapKeyTypeSize = config->getTypeSize(keytypeName); - mMapKeyAlignment = std::max(config->getAlingment(keytypeName), config->getAlingment(valuetypeName)); + mMapKeyTypeSize = config->getTypeSize(mMapKeyType); if (mMapValueType.empty()) - setWindowTitle(QString("std::unordered_set<%1>").arg(QString::fromStdString(keytypeName))); + mMapAlignment = config->getAlingment(mMapKeyType); else - setWindowTitle(QString("std::unordered_map<%1, %2>").arg(QString::fromStdString(keytypeName), QString::fromStdString(valuetypeName))); + mMapAlignment = std::max(config->getAlingment(mMapKeyType), config->getAlingment(mMapValueType)); + + if (mMapValueType.empty()) + setWindowTitle(QString("std::unordered_set<%1>").arg(QString::fromStdString(mMapKeyType))); + else + setWindowTitle(QString("std::unordered_map<%1, %2>").arg(QString::fromStdString(mMapKeyType), QString::fromStdString(mMapValueType))); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColComment); reloadContainer(); @@ -30,60 +34,21 @@ void S2Plugin::ViewStdUnorderedMap::reloadContainer() auto config = Configuration::get(); mMainTreeView->clear(); - MemoryField key_field; + MemoryField key_field = config->nameToMemoryField(mMapKeyType); key_field.name = "key"; - if (config->isPermanentPointer(mMapKeyType)) - { - key_field.type = MemoryFieldType::DefaultStructType; - key_field.jsonName = mMapKeyType; - key_field.isPointer = true; - } - else if (config->isJsonStruct(mMapKeyType)) - { - key_field.type = MemoryFieldType::DefaultStructType; - key_field.jsonName = mMapKeyType; - } - else if (auto type = config->getBuiltInType(mMapKeyType); type != MemoryFieldType::None) - { - key_field.type = type; - if (Configuration::isPointerType(type)) - key_field.isPointer = true; - } - else - { - dprintf("unknown type in ViewStdMap::refreshMapContents() (%s)\n", mMapKeyType.c_str()); + if (key_field.type == MemoryFieldType::None) return; - } MemoryField value_field; if (!mMapValueType.empty()) // if not StdSet { + value_field = config->nameToMemoryField(mMapValueType); value_field.name = "value"; - if (config->isPermanentPointer(mMapValueType)) - { - value_field.type = MemoryFieldType::DefaultStructType; - value_field.jsonName = mMapValueType; - value_field.isPointer = true; - } - else if (config->isJsonStruct(mMapValueType)) - { - value_field.type = MemoryFieldType::DefaultStructType; - value_field.jsonName = mMapValueType; - } - else if (auto type = config->getBuiltInType(mMapValueType); type != MemoryFieldType::None) - { - value_field.type = type; - if (Configuration::isPointerType(type)) - value_field.isPointer = true; - } - else - { - dprintf("unknown type in ViewStdMap::refreshMapContents() (%s)\n", mMapValueType.c_str()); + if (value_field.type == MemoryFieldType::None) return; - } } - StdUnorderedMap the_map{mMapAddress, mMapKeyTypeSize, mMapKeyAlignment}; + StdUnorderedMap the_map{mMapAddress, mMapKeyTypeSize, mMapAlignment}; auto _end = the_map.end(); auto _cur = the_map.begin(); MemoryField parent_field; From 5d4aea6f152a443ecff0b960aa9a5cc9b5db60b4 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:19:15 +0200 Subject: [PATCH 13/20] add `explicit` to the constructors just in case --- include/Data/Entity.h | 2 +- include/Data/EntityList.h | 12 ++++++------ include/Data/IDNameList.h | 2 +- include/Data/State.h | 2 +- include/Data/StdList.h | 10 +++++----- include/Data/StdMap.h | 4 ++-- include/Data/StdString.h | 2 +- include/QtHelpers/AbstractContainerView.h | 2 +- include/QtHelpers/AbstractDatabaseView.h | 2 +- include/QtHelpers/DialogEditSimpleValue.h | 2 +- include/QtHelpers/DialogEditState.h | 2 +- include/QtHelpers/DialogEditString.h | 2 +- include/QtHelpers/ItemModelGatherVirtualData.h | 4 ++-- include/QtHelpers/ItemModelLoggerFields.h | 2 +- include/QtHelpers/ItemModelLoggerSamples.h | 2 +- include/QtHelpers/ItemModelStates.h | 4 ++-- include/QtHelpers/ItemModelVirtualFunctions.h | 5 +++-- include/QtHelpers/ItemModelVirtualTable.h | 4 ++-- include/QtHelpers/TableViewLogger.h | 2 +- include/QtHelpers/TreeViewMemoryFields.h | 2 +- include/QtHelpers/WidgetAutorefresh.h | 2 +- include/QtHelpers/WidgetMemoryView.h | 2 +- include/QtHelpers/WidgetSamplesPlot.h | 2 +- include/QtHelpers/WidgetSpelunkyLevel.h | 2 +- include/QtHelpers/WidgetSpelunkyRooms.h | 2 +- src/QtHelpers/ItemModelVirtualTable.cpp | 2 +- 26 files changed, 41 insertions(+), 40 deletions(-) diff --git a/include/Data/Entity.h b/include/Data/Entity.h index cb9d09d..8762212 100644 --- a/include/Data/Entity.h +++ b/include/Data/Entity.h @@ -12,7 +12,7 @@ namespace S2Plugin class Entity { public: - Entity(uintptr_t addr) : mEntityPtr(addr){}; + explicit Entity(uintptr_t addr) : mEntityPtr(addr){}; Entity() = delete; std::string entityClassName() const; diff --git a/include/Data/EntityList.h b/include/Data/EntityList.h index 2ccad79..c82d042 100644 --- a/include/Data/EntityList.h +++ b/include/Data/EntityList.h @@ -13,7 +13,7 @@ namespace S2Plugin class EntityList { public: - EntityList(uintptr_t address) + explicit EntityList(uintptr_t address) { Script::Memory::Read(address, this, sizeof(EntityList), nullptr); }; @@ -36,7 +36,7 @@ namespace S2Plugin struct Iterator { // Iterator(){}; - Iterator(const EntityList entityList, uint32_t index) noexcept : Iterator(entityList.begin()) + explicit Iterator(const EntityList entityList, uint32_t index) noexcept : Iterator(entityList.begin()) { advance(index); } @@ -84,7 +84,7 @@ namespace S2Plugin } Entity entity() const { - return Script::Memory::ReadQword(addr.first); + return Entity{Script::Memory::ReadQword(addr.first)}; } uint32_t uid() const { @@ -92,20 +92,20 @@ namespace S2Plugin } private: - Iterator(uintptr_t entitiesAddress, uintptr_t uidsAddress) : addr(entitiesAddress, uidsAddress){}; + explicit Iterator(uintptr_t entitiesAddress, uintptr_t uidsAddress) : addr(entitiesAddress, uidsAddress){}; std::pair addr; friend class EntityList; }; Iterator begin() const { - return {entities(), uids()}; + return Iterator{entities(), uids()}; } Iterator end() const { uintptr_t entitiesEnd = entities() + size() * sizeof(uintptr_t); uintptr_t uidsEnd = uids() + size() * sizeof(uint32_t); - return {entitiesEnd, uidsEnd}; + return Iterator{entitiesEnd, uidsEnd}; } const Iterator cbegin() const { diff --git a/include/Data/IDNameList.h b/include/Data/IDNameList.h index f56805e..f54a42e 100644 --- a/include/Data/IDNameList.h +++ b/include/Data/IDNameList.h @@ -11,7 +11,7 @@ namespace S2Plugin class IDNameList { protected: - IDNameList(const std::string& relFilePath, const std::regex& regex); + explicit IDNameList(const std::string& relFilePath, const std::regex& regex); public: uint32_t idForName(const std::string& name) const; diff --git a/include/Data/State.h b/include/Data/State.h index 6706aec..aa5870d 100644 --- a/include/Data/State.h +++ b/include/Data/State.h @@ -6,7 +6,7 @@ namespace S2Plugin { struct State { - State(uintptr_t addr) : mStatePtr(addr){}; + explicit State(uintptr_t addr) : mStatePtr(addr){}; uintptr_t ptr() const { diff --git a/include/Data/StdList.h b/include/Data/StdList.h index 14b0e6f..86c2f88 100644 --- a/include/Data/StdList.h +++ b/include/Data/StdList.h @@ -9,19 +9,19 @@ namespace S2Plugin class OldStdList { public: - OldStdList(uintptr_t address) : _end(address){}; + explicit OldStdList(uintptr_t address) : _end(address){}; struct Node { - Node(uintptr_t address) : nodeAddress(address){}; + explicit Node(uintptr_t address) : nodeAddress(address){}; Node prev() const { - return Script::Memory::ReadQword(nodeAddress); + return Node{Script::Memory::ReadQword(nodeAddress)}; } Node next() const { - return Script::Memory::ReadQword(nodeAddress + sizeof(uintptr_t)); + return Node{Script::Memory::ReadQword(nodeAddress + sizeof(uintptr_t))}; } uintptr_t value_ptr() const noexcept { @@ -113,7 +113,7 @@ namespace S2Plugin { uintptr_t data[2]; Script::Memory::Read(address, &data, 2 * sizeof(uintptr_t), nullptr); - mHead = data[0]; + mHead = Node{data[0]}; mSize = data[1]; } Node begin() const diff --git a/include/Data/StdMap.h b/include/Data/StdMap.h index 9697e83..a68cd6c 100644 --- a/include/Data/StdMap.h +++ b/include/Data/StdMap.h @@ -23,7 +23,7 @@ namespace S2Plugin { // only for the template template - StdMap(uintptr_t addr, std::enable_if_t || !std::is_same_v, int> = 0) + explicit StdMap(uintptr_t addr, std::enable_if_t || !std::is_same_v, int> = 0) { uintptr_t data[2]; Script::Memory::Read(addr, &data, sizeof(data), nullptr); @@ -158,7 +158,7 @@ namespace S2Plugin private: Node() = default; - Node(size_t addr) : node_ptr(addr){}; + explicit Node(size_t addr) : node_ptr(addr){}; void set_offsets(size_t keytype_size = sizeof(Key), uint8_t key_alignment = alignof(Key), uint8_t value_alignment = alignof(Value)) noexcept { // key and value in map are treated as std::pair diff --git a/include/Data/StdString.h b/include/Data/StdString.h index 4a57d94..923dad4 100644 --- a/include/Data/StdString.h +++ b/include/Data/StdString.h @@ -9,7 +9,7 @@ namespace S2Plugin template struct StdBasicString { - StdBasicString(size_t addr) : addr(addr){}; + explicit StdBasicString(size_t addr) : addr(addr){}; size_t size() const { return Script::Memory::ReadQword(addr + 0x10); diff --git a/include/QtHelpers/AbstractContainerView.h b/include/QtHelpers/AbstractContainerView.h index 5b64b17..04a3392 100644 --- a/include/QtHelpers/AbstractContainerView.h +++ b/include/QtHelpers/AbstractContainerView.h @@ -13,7 +13,7 @@ namespace S2Plugin { Q_OBJECT public: - AbstractContainerView(QWidget* parent = nullptr); + explicit AbstractContainerView(QWidget* parent = nullptr); protected: QSize sizeHint() const override; diff --git a/include/QtHelpers/AbstractDatabaseView.h b/include/QtHelpers/AbstractDatabaseView.h index 6eb462e..1a088c4 100644 --- a/include/QtHelpers/AbstractDatabaseView.h +++ b/include/QtHelpers/AbstractDatabaseView.h @@ -37,7 +37,7 @@ namespace S2Plugin { Q_OBJECT public: - AbstractDatabaseView(MemoryFieldType type, QWidget* parent = nullptr); + explicit AbstractDatabaseView(MemoryFieldType type, QWidget* parent = nullptr); virtual void showID(ID_type id) = 0; protected: diff --git a/include/QtHelpers/DialogEditSimpleValue.h b/include/QtHelpers/DialogEditSimpleValue.h index ff7d92b..3f1da70 100644 --- a/include/QtHelpers/DialogEditSimpleValue.h +++ b/include/QtHelpers/DialogEditSimpleValue.h @@ -17,7 +17,7 @@ namespace S2Plugin Q_OBJECT public: - DialogEditSimpleValue(const QString& fieldName, uintptr_t memoryAddress, MemoryFieldType type, QWidget* parent = nullptr); + explicit DialogEditSimpleValue(const QString& fieldName, uintptr_t memoryAddress, MemoryFieldType type, QWidget* parent = nullptr); protected: QSize minimumSizeHint() const override; diff --git a/include/QtHelpers/DialogEditState.h b/include/QtHelpers/DialogEditState.h index 9fd5fa5..f5691a2 100644 --- a/include/QtHelpers/DialogEditState.h +++ b/include/QtHelpers/DialogEditState.h @@ -20,7 +20,7 @@ namespace S2Plugin Q_OBJECT public: - DialogEditState(const QString& fieldName, const std::string& refName, uintptr_t memoryAddress, MemoryFieldType type, QWidget* parent = nullptr); + explicit DialogEditState(const QString& fieldName, const std::string& refName, uintptr_t memoryAddress, MemoryFieldType type, QWidget* parent = nullptr); protected: QSize minimumSizeHint() const override; diff --git a/include/QtHelpers/DialogEditString.h b/include/QtHelpers/DialogEditString.h index 59d9391..892516f 100644 --- a/include/QtHelpers/DialogEditString.h +++ b/include/QtHelpers/DialogEditString.h @@ -17,7 +17,7 @@ namespace S2Plugin public: // size without the last char (null termination) - DialogEditString(const QString& fieldName, QString initialValue, uintptr_t memoryAddress, int size, MemoryFieldType type, QWidget* parent = nullptr); + explicit DialogEditString(const QString& fieldName, QString initialValue, uintptr_t memoryAddress, int size, MemoryFieldType type, QWidget* parent = nullptr); protected: QSize minimumSizeHint() const override; diff --git a/include/QtHelpers/ItemModelGatherVirtualData.h b/include/QtHelpers/ItemModelGatherVirtualData.h index ad54192..3220e47 100644 --- a/include/QtHelpers/ItemModelGatherVirtualData.h +++ b/include/QtHelpers/ItemModelGatherVirtualData.h @@ -40,7 +40,7 @@ namespace S2Plugin Q_OBJECT public: - ItemModelGatherVirtualData(QObject* parent = nullptr) : QAbstractItemModel(parent) + explicit ItemModelGatherVirtualData(QObject* parent = nullptr) : QAbstractItemModel(parent) { parseJSON(); }; @@ -91,7 +91,7 @@ namespace S2Plugin Q_OBJECT public: - SortFilterProxyModelGatherVirtualData(QObject* parent = nullptr) : QSortFilterProxyModel(parent){}; + explicit SortFilterProxyModelGatherVirtualData(QObject* parent = nullptr) : QSortFilterProxyModel(parent){}; bool filterAcceptsRow(int sourceRow, const QModelIndex&) const override { diff --git a/include/QtHelpers/ItemModelLoggerFields.h b/include/QtHelpers/ItemModelLoggerFields.h index 2a61457..6b52894 100644 --- a/include/QtHelpers/ItemModelLoggerFields.h +++ b/include/QtHelpers/ItemModelLoggerFields.h @@ -12,7 +12,7 @@ namespace S2Plugin { Q_OBJECT public: - ItemModelLoggerFields(Logger* logger, QObject* parent = nullptr) : QAbstractItemModel(parent), mLogger(logger){}; + explicit ItemModelLoggerFields(Logger* logger, QObject* parent = nullptr) : QAbstractItemModel(parent), mLogger(logger){}; void removeRow(int index) { diff --git a/include/QtHelpers/ItemModelLoggerSamples.h b/include/QtHelpers/ItemModelLoggerSamples.h index ed3d77a..0b3fe4b 100644 --- a/include/QtHelpers/ItemModelLoggerSamples.h +++ b/include/QtHelpers/ItemModelLoggerSamples.h @@ -12,7 +12,7 @@ namespace S2Plugin { Q_OBJECT public: - ItemModelLoggerSamples(Logger* logger, QObject* parent = nullptr) : QAbstractItemModel(parent), mLogger(logger){}; + explicit ItemModelLoggerSamples(Logger* logger, QObject* parent = nullptr) : QAbstractItemModel(parent), mLogger(logger){}; void reset() { diff --git a/include/QtHelpers/ItemModelStates.h b/include/QtHelpers/ItemModelStates.h index 6272fd5..fc39964 100644 --- a/include/QtHelpers/ItemModelStates.h +++ b/include/QtHelpers/ItemModelStates.h @@ -14,7 +14,7 @@ namespace S2Plugin { Q_OBJECT public: - ItemModelStates(const std::vector>& states, QObject* parent = nullptr) : QAbstractItemModel(parent), mStates(states){}; + explicit ItemModelStates(const std::vector>& states, QObject* parent = nullptr) : QAbstractItemModel(parent), mStates(states){}; Qt::ItemFlags flags(const QModelIndex&) const override { @@ -59,7 +59,7 @@ namespace S2Plugin Q_OBJECT public: - SortFilterProxyModelStates(const std::vector>& states, QObject* parent = nullptr) : QSortFilterProxyModel(parent), mStates(states) + explicit SortFilterProxyModelStates(const std::vector>& states, QObject* parent = nullptr) : QSortFilterProxyModel(parent), mStates(states) { setSortRole(Qt::UserRole); } diff --git a/include/QtHelpers/ItemModelVirtualFunctions.h b/include/QtHelpers/ItemModelVirtualFunctions.h index 9ff6863..3040718 100644 --- a/include/QtHelpers/ItemModelVirtualFunctions.h +++ b/include/QtHelpers/ItemModelVirtualFunctions.h @@ -22,7 +22,8 @@ namespace S2Plugin { Q_OBJECT public: - ItemModelVirtualFunctions(const std::string& typeName, uintptr_t memoryAddress, QObject* parent = nullptr) : QAbstractItemModel(parent), mTypeName(typeName), mMemoryAddress(memoryAddress){}; + explicit ItemModelVirtualFunctions(const std::string& typeName, uintptr_t memoryAddress, QObject* parent = nullptr) + : QAbstractItemModel(parent), mTypeName(typeName), mMemoryAddress(memoryAddress){}; Qt::ItemFlags flags(const QModelIndex&) const override { @@ -54,7 +55,7 @@ namespace S2Plugin Q_OBJECT public: - SortFilterProxyModelVirtualFunctions(QObject* parent = nullptr) : QSortFilterProxyModel(parent) + explicit SortFilterProxyModelVirtualFunctions(QObject* parent = nullptr) : QSortFilterProxyModel(parent) { setSortRole(gsRoleFunctionIndex); } diff --git a/include/QtHelpers/ItemModelVirtualTable.h b/include/QtHelpers/ItemModelVirtualTable.h index d5835c7..28d60b5 100644 --- a/include/QtHelpers/ItemModelVirtualTable.h +++ b/include/QtHelpers/ItemModelVirtualTable.h @@ -19,7 +19,7 @@ namespace S2Plugin Q_OBJECT public: - ItemModelVirtualTable(QObject* parent = nullptr); + explicit ItemModelVirtualTable(QObject* parent = nullptr); Qt::ItemFlags flags(const QModelIndex&) const override { @@ -53,7 +53,7 @@ namespace S2Plugin Q_OBJECT public: - SortFilterProxyModelVirtualTable(QObject* parent = nullptr); + explicit SortFilterProxyModelVirtualTable(QObject* parent = nullptr); bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const override; void setShowImportedSymbols(bool b) diff --git a/include/QtHelpers/TableViewLogger.h b/include/QtHelpers/TableViewLogger.h index fc081fd..2103079 100644 --- a/include/QtHelpers/TableViewLogger.h +++ b/include/QtHelpers/TableViewLogger.h @@ -23,7 +23,7 @@ namespace S2Plugin { Q_OBJECT public: - TableViewLogger(Logger* logger, QWidget* parent = nullptr); + explicit TableViewLogger(Logger* logger, QWidget* parent = nullptr); protected: void dragEnterEvent(QDragEnterEvent* event) override; diff --git a/include/QtHelpers/TreeViewMemoryFields.h b/include/QtHelpers/TreeViewMemoryFields.h index c4b0fa3..ee43550 100644 --- a/include/QtHelpers/TreeViewMemoryFields.h +++ b/include/QtHelpers/TreeViewMemoryFields.h @@ -50,7 +50,7 @@ namespace S2Plugin { Q_OBJECT public: - TreeViewMemoryFields(QWidget* parent = nullptr); + explicit TreeViewMemoryFields(QWidget* parent = nullptr); void addMemoryFields(const std::vector& fields, const std::string& mainName, uintptr_t structAddr, size_t initialDelta = 0, uint8_t deltaPrefixCount = 0, QStandardItem* parent = nullptr); diff --git a/include/QtHelpers/WidgetAutorefresh.h b/include/QtHelpers/WidgetAutorefresh.h index ea4c014..a6ae460 100644 --- a/include/QtHelpers/WidgetAutorefresh.h +++ b/include/QtHelpers/WidgetAutorefresh.h @@ -14,7 +14,7 @@ namespace S2Plugin { Q_OBJECT public: - WidgetAutorefresh(int initialInterval, QWidget* parrent = nullptr); + explicit WidgetAutorefresh(int initialInterval, QWidget* parrent = nullptr); signals: void refresh(); diff --git a/include/QtHelpers/WidgetMemoryView.h b/include/QtHelpers/WidgetMemoryView.h index 5897d0a..070ca96 100644 --- a/include/QtHelpers/WidgetMemoryView.h +++ b/include/QtHelpers/WidgetMemoryView.h @@ -36,7 +36,7 @@ namespace S2Plugin Q_OBJECT public: - WidgetMemoryView(QWidget* parent = nullptr); + explicit WidgetMemoryView(QWidget* parent = nullptr); QSize minimumSizeHint() const override; QSize sizeHint() const override; diff --git a/include/QtHelpers/WidgetSamplesPlot.h b/include/QtHelpers/WidgetSamplesPlot.h index 67545f0..c2b4ed0 100644 --- a/include/QtHelpers/WidgetSamplesPlot.h +++ b/include/QtHelpers/WidgetSamplesPlot.h @@ -15,7 +15,7 @@ namespace S2Plugin { Q_OBJECT public: - WidgetSamplesPlot(Logger* logger, QWidget* parent = nullptr) : QWidget(parent), mLogger(logger) + explicit WidgetSamplesPlot(Logger* logger, QWidget* parent = nullptr) : QWidget(parent), mLogger(logger) { setMouseTracking(true); setCursor(Qt::CrossCursor); diff --git a/include/QtHelpers/WidgetSpelunkyLevel.h b/include/QtHelpers/WidgetSpelunkyLevel.h index b4d0e59..0917f36 100644 --- a/include/QtHelpers/WidgetSpelunkyLevel.h +++ b/include/QtHelpers/WidgetSpelunkyLevel.h @@ -17,7 +17,7 @@ namespace S2Plugin { public: EntityToPaint() = default; - EntityToPaint(uintptr_t addr, QBrush col) : ent(addr), color(col){}; + explicit EntityToPaint(uintptr_t addr, QBrush col) : ent(addr), color(col){}; protected: Entity ent; diff --git a/include/QtHelpers/WidgetSpelunkyRooms.h b/include/QtHelpers/WidgetSpelunkyRooms.h index 2d1a7d6..b5e26e0 100644 --- a/include/QtHelpers/WidgetSpelunkyRooms.h +++ b/include/QtHelpers/WidgetSpelunkyRooms.h @@ -20,7 +20,7 @@ namespace S2Plugin Q_OBJECT public: - WidgetSpelunkyRooms(const std::string& fieldName, QWidget* parent = nullptr); + explicit WidgetSpelunkyRooms(const std::string& fieldName, QWidget* parent = nullptr); QSize minimumSizeHint() const override; QSize sizeHint() const override; diff --git a/src/QtHelpers/ItemModelVirtualTable.cpp b/src/QtHelpers/ItemModelVirtualTable.cpp index fb4b134..6b61c5f 100644 --- a/src/QtHelpers/ItemModelVirtualTable.cpp +++ b/src/QtHelpers/ItemModelVirtualTable.cpp @@ -79,7 +79,7 @@ void S2Plugin::ItemModelVirtualTable::detectEntities() for (auto x = 0; x < maximum; ++x) { auto entityPtr = layerEntities + (x * sizeof(size_t)); - Entity entity = Script::Memory::ReadQword(entityPtr); + Entity entity{Script::Memory::ReadQword(entityPtr)}; auto entityVTableOffset = Script::Memory::ReadQword(entity.ptr()); auto entityName = entity.entityTypeName(); From b0757ce9920f97c587d3d6cc18b7817ab69889d8 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 16 Jun 2024 20:39:05 +0200 Subject: [PATCH 14/20] add pagination, some fixes and rearrangements in the code, adjust sizes of memory tree a little --- CMakeLists.txt | 1 + include/Configuration.h | 50 +++---- include/Data/StdList.h | 4 + include/QtHelpers/AbstractContainerView.h | 2 + include/QtHelpers/TreeViewMemoryFields.h | 20 +-- include/QtHelpers/WidgetPagination.h | 160 ++++++++++++++++++++++ include/Views/ViewEntityList.h | 2 + include/Views/ViewStdList.h | 3 +- include/Views/ViewStdMap.h | 11 +- include/Views/ViewStdUnorderedMap.h | 8 +- include/Views/ViewStdVector.h | 4 +- include/Views/ViewStruct.h | 19 +++ src/Configuration.cpp | 1 + src/QtHelpers/AbstractContainerView.cpp | 6 + src/QtHelpers/AbstractDatabaseView.cpp | 15 +- src/QtHelpers/TreeViewMemoryFields.cpp | 116 ++++++++++------ src/Views/ViewEntities.cpp | 15 +- src/Views/ViewEntity.cpp | 10 +- src/Views/ViewEntityList.cpp | 30 ++-- src/Views/ViewJournalPage.cpp | 11 +- src/Views/ViewLevelGen.cpp | 13 +- src/Views/ViewStdList.cpp | 91 +++++++----- src/Views/ViewStdMap.cpp | 86 +++++++----- src/Views/ViewStdUnorderedMap.cpp | 91 +++++++----- src/Views/ViewStdVector.cpp | 70 +++++----- src/Views/ViewStruct.cpp | 74 +++++++--- 26 files changed, 620 insertions(+), 293 deletions(-) create mode 100644 include/QtHelpers/WidgetPagination.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 249993f..65c4fc4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,7 @@ x64dbg_plugin(${PROJECT_NAME} include/QtHelpers/AbstractContainerView.h include/QtHelpers/WidgetAutorefresh.h include/QtHelpers/LongLongSpinBox.h + include/QtHelpers/WidgetPagination.h src/Spelunky2.cpp src/Configuration.cpp src/Data/EntityDB.cpp diff --git a/include/Configuration.h b/include/Configuration.h index d3d0381..dc30577 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -146,7 +146,7 @@ namespace S2Plugin VirtualFunction(size_t i, std::string n, std::string p, std::string r, std::string t) : index(i), name(n), params(p), returnValue(r), type(t){}; }; - struct MemoryField + struct MemoryField // TODO this got big over time, consider size optimaizations { std::string name; MemoryFieldType type{MemoryFieldType::None}; @@ -204,7 +204,7 @@ namespace S2Plugin { return get() != nullptr; } - + // Accessors const std::unordered_map& entityClassHierarchy() const noexcept { return mEntityClassHierarchy; @@ -213,6 +213,19 @@ namespace S2Plugin { return mDefaultEntityClassTypes; } + const EntityNamesList& entityList() const noexcept + { + return entityNames; + } + const ParticleEmittersList& particleEmittersList() const noexcept + { + return particleEmitters; + } + const std::vector& getJournalPageNames() const noexcept + { + return mJournalPages; + } + // std::vector classHierarchyOfEntity(const std::string& entityName) const; const std::vector& typeFields(const MemoryFieldType& type) const; @@ -236,40 +249,27 @@ namespace S2Plugin uint8_t getAlingment(const std::string& type) const; uint8_t getAlingment(MemoryFieldType type) const; uint8_t getAlingment(const MemoryField& type) const; - bool isPermanentPointer(const std::string& type) const - { - return std::find(mPointerTypes.begin(), mPointerTypes.end(), type) != mPointerTypes.end(); - } - bool isPermanentPointer(const std::string_view type) const - { - return std::find(mPointerTypes.begin(), mPointerTypes.end(), type) != mPointerTypes.end(); - } - bool isJsonStruct(const std::string& type) const - { - return mTypeFieldsStructs.find(type) != mTypeFieldsStructs.end(); - } std::string flagTitle(const std::string& fieldName, uint8_t flagNumber) const; std::string stateTitle(const std::string& fieldName, int64_t state) const; const std::vector>& refTitlesOfField(const std::string& fieldName) const; size_t getTypeSize(const std::string& typeName, bool entitySubclass = false); - const EntityNamesList& entityList() const - { - return entityNames; - }; - - const ParticleEmittersList& particleEmittersList() const - { - return particleEmitters; - } RoomCode roomCodeForID(uint16_t code) const; std::string getEntityName(uint32_t type) const; - const std::vector& getJournalPageNames() + bool isPermanentPointer(const std::string& type) const { - return mJournalPages; + return std::find(mPointerTypes.begin(), mPointerTypes.end(), type) != mPointerTypes.end(); + } + bool isPermanentPointer(const std::string_view type) const + { + return std::find(mPointerTypes.begin(), mPointerTypes.end(), type) != mPointerTypes.end(); + } + bool isJsonStruct(const std::string& type) const + { + return mTypeFieldsStructs.find(type) != mTypeFieldsStructs.end(); } private: diff --git a/include/Data/StdList.h b/include/Data/StdList.h index 86c2f88..e9979b9 100644 --- a/include/Data/StdList.h +++ b/include/Data/StdList.h @@ -98,6 +98,10 @@ namespace S2Plugin { return _end.next(); } + bool isValid() const + { + return Script::Memory::IsValidPtr(_end.next().address()) && Script::Memory::IsValidPtr(_end.prev().address()); + } private: Node _end; diff --git a/include/QtHelpers/AbstractContainerView.h b/include/QtHelpers/AbstractContainerView.h index 04a3392..53c1000 100644 --- a/include/QtHelpers/AbstractContainerView.h +++ b/include/QtHelpers/AbstractContainerView.h @@ -8,6 +8,7 @@ namespace S2Plugin { class TreeViewMemoryFields; + class WidgetPagination; class AbstractContainerView : public QWidget { @@ -21,5 +22,6 @@ namespace S2Plugin virtual void reloadContainer() = 0; TreeViewMemoryFields* mMainTreeView; + WidgetPagination* mPagination; }; } // namespace S2Plugin diff --git a/include/QtHelpers/TreeViewMemoryFields.h b/include/QtHelpers/TreeViewMemoryFields.h index ee43550..df89ca9 100644 --- a/include/QtHelpers/TreeViewMemoryFields.h +++ b/include/QtHelpers/TreeViewMemoryFields.h @@ -62,12 +62,13 @@ namespace S2Plugin { mEnableChangeHighlighting = b; } - + void setEnableTopBranchDrawing(bool b) noexcept + { + drawTopBranch = b; + } void updateTree(uintptr_t newAddr, uintptr_t newComparisonAddr = 0, bool initial = false); void updateRow(int row, std::optional newAddr = std::nullopt, std::optional newAddrComparison = std::nullopt, QStandardItem* parent = nullptr, bool disableChangeHighlightingForField = false); - - ColumnFilter activeColumns; void labelAll(std::string_view prefix); void expandLast(); @@ -80,24 +81,27 @@ namespace S2Plugin { updateTree(0, 0, false); } + private slots: + void cellClicked(const QModelIndex& index); protected: void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; void dropEvent(QDropEvent* event) override; void startDrag(Qt::DropActions supportedActions) override; - + void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override; signals: void memoryFieldValueUpdated(int row, QStandardItem* parrent); void levelGenRoomsPointerClicked(); void offsetDropped(uintptr_t offset); - private slots: - void cellClicked(const QModelIndex& index); + public: + ColumnFilter activeColumns; private: - QStandardItemModel* mModel; - std::array mSavedColumnWidths = {0}; bool mEnableChangeHighlighting = true; + bool drawTopBranch = true; + std::array mSavedColumnWidths = {0}; + QStandardItemModel* mModel; }; } // namespace S2Plugin diff --git a/include/QtHelpers/WidgetPagination.h b/include/QtHelpers/WidgetPagination.h new file mode 100644 index 0000000..15e0dab --- /dev/null +++ b/include/QtHelpers/WidgetPagination.h @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace S2Plugin +{ + + class WidgetPagination : public QWidget + { + Q_OBJECT + public: + explicit WidgetPagination(QWidget* parent = nullptr) : QWidget(parent) + { + QHBoxLayout* layout = new QHBoxLayout(this); + layout->addWidget(new QLabel("Page size:")); + mComboBox = new QComboBox(this); + mComboBox->addItems({"50", "100", "200", "500", "1000"}); + mComboBox->setCurrentIndex(1); + layout->addWidget(mComboBox); + QObject::connect(mComboBox, static_cast(&QComboBox::currentIndexChanged), + [this]() + { + updateSpinBoxRange(); + emit pageUpdate(); + }); + layout->addStretch(); + mLeftEnd = new QPushButton("|<", this); + mLeftEnd->setDisabled(true); + mLeftEnd->setMaximumWidth(mLeftEnd->sizeHint().height()); + mLeft = new QPushButton("<", this); + mLeft->setDisabled(true); + mLeft->setMaximumWidth(mLeft->sizeHint().height()); + mSpinBox = new QSpinBox(this); + mSpinBox->setRange(1, 1); + mSpinBox->setButtonSymbols(QSpinBox::NoButtons); + mSpinBox->setAlignment(Qt::AlignCenter); + mRight = new QPushButton(">", this); + mRight->setDisabled(true); + mRight->setMaximumWidth(mRight->sizeHint().height()); + mRightEnd = new QPushButton(">|", this); + mRightEnd->setDisabled(true); + mRightEnd->setMaximumWidth(mRightEnd->sizeHint().height()); + QObject::connect(mLeftEnd, &QPushButton::clicked, [this]() { setPage(1); }); + QObject::connect(mLeft, &QPushButton::clicked, [this]() { setPage(mCurrentPage - 1); }); + QObject::connect(mSpinBox, static_cast(&QSpinBox::valueChanged), [this](int value) { setPage(value); }); + QObject::connect(mRight, &QPushButton::clicked, [this]() { setPage(mCurrentPage + 1); }); + QObject::connect(mRightEnd, &QPushButton::clicked, [this]() { setPage(-1); }); + layout->addWidget(mLeftEnd); + layout->addWidget(mLeft); + layout->addWidget(mSpinBox); + layout->addWidget(mRight); + layout->addWidget(mRightEnd); + } + void setPage(int page) + { + if (page == (int)mCurrentPage) + return; + + auto count = pageCount(); + if (page == -1 || (size_t)page >= count) + { + page = count; + } + else if (page < 1) + { + page = 1; + } + mCurrentPage = page; + mLeftEnd->setDisabled(page == 1); + mLeft->setDisabled(page == 1); + mRight->setDisabled((size_t)page == count); + mRightEnd->setDisabled((size_t)page == count); + mSpinBox->setValue(page); + emit pageUpdate(); + } + void setSize(size_t size) + { + mSize = size; + updateSpinBoxRange(); + } + std::pair getRange() const + { + uint pageIndex = mCurrentPage - 1; + size_t upper = (size_t)pageIndex * recordsPerPage() + recordsPerPage(); + if (upper > mSize) + { + upper = mSize; + } + return {pageIndex * recordsPerPage(), upper}; + } + void setPageSizes(const std::vector& sizes) + { + mPageSizes = sizes; + mComboBox->clear(); + for (auto size : mPageSizes) + { + mComboBox->addItem(QString::number(size)); + } + updateSpinBoxRange(); + } + uint recordsPerPage() const + { + return mPageSizes[mComboBox->currentIndex()]; + } + uint getCurrentPage() const + { + return mCurrentPage; + } + + signals: + void pageUpdate(); + + private: + uint pageCount() const + { + uint count = (uint)(mSize / recordsPerPage()); + if (mSize % recordsPerPage() > 0) + { + count++; + } + return count == 0 ? 1 : count; + } + void updateSpinBoxRange() + { + auto count = pageCount(); + mSpinBox->setRange(1, count); + if (mCurrentPage > count) + { + setPage(count); + } + else if (mCurrentPage == count) + { + mRightEnd->setDisabled(true); + mRight->setDisabled(true); + } + else + { + mRightEnd->setDisabled(false); + mRight->setDisabled(false); + } + } + QComboBox* mComboBox; + QSpinBox* mSpinBox; + QPushButton* mLeftEnd; + QPushButton* mLeft; + QPushButton* mRight; + QPushButton* mRightEnd; + + std::vector mPageSizes = {50, 100, 200, 500, 1000}; + size_t mSize = 0; + uint mCurrentPage = 1; + }; +}; // namespace S2Plugin diff --git a/include/Views/ViewEntityList.h b/include/Views/ViewEntityList.h index a99ee6a..017ca82 100644 --- a/include/Views/ViewEntityList.h +++ b/include/Views/ViewEntityList.h @@ -1,5 +1,6 @@ #pragma once +#include "Configuration.h" #include "QtHelpers/AbstractContainerView.h" #include #include @@ -17,6 +18,7 @@ namespace S2Plugin void reloadContainer() override; private: + MemoryField mEntityField; uintptr_t mEntityListAddress; }; } // namespace S2Plugin diff --git a/include/Views/ViewStdList.h b/include/Views/ViewStdList.h index fa4d805..d379f8d 100644 --- a/include/Views/ViewStdList.h +++ b/include/Views/ViewStdList.h @@ -1,5 +1,6 @@ #pragma once +#include "Configuration.h" #include "QtHelpers/AbstractContainerView.h" #include #include @@ -17,7 +18,7 @@ namespace S2Plugin void reloadContainer() override; private: - std::string mListType; + MemoryField mValueField; uintptr_t mListAddress; bool mOldType; }; diff --git a/include/Views/ViewStdMap.h b/include/Views/ViewStdMap.h index 3574500..d933ff9 100644 --- a/include/Views/ViewStdMap.h +++ b/include/Views/ViewStdMap.h @@ -1,5 +1,6 @@ #pragma once +#include "Configuration.h" #include "QtHelpers/AbstractContainerView.h" #include #include @@ -7,7 +8,6 @@ namespace S2Plugin { - class ViewStdMap : public AbstractContainerView { Q_OBJECT @@ -17,12 +17,13 @@ namespace S2Plugin protected: void reloadContainer() override; + protected slots: + void onItemCollapsed(const QModelIndex& index); + private: - std::string mMapKeyType; - std::string mMapValueType; + MemoryField mKeyField; + MemoryField mValueField; uintptr_t mMapAddress; - size_t mMapKeyTypeSize; - size_t mMapValueTypeSize; uint8_t mMapKeyAlignment; uint8_t mMapValueAlignment; }; diff --git a/include/Views/ViewStdUnorderedMap.h b/include/Views/ViewStdUnorderedMap.h index f599887..83c6cca 100644 --- a/include/Views/ViewStdUnorderedMap.h +++ b/include/Views/ViewStdUnorderedMap.h @@ -1,5 +1,6 @@ #pragma once +#include "Configuration.h" #include "QtHelpers/AbstractContainerView.h" #include #include @@ -16,12 +17,13 @@ namespace S2Plugin protected: void reloadContainer() override; + protected slots: + void onItemCollapsed(const QModelIndex& index); private: - std::string mMapKeyType; - std::string mMapValueType; + MemoryField mKeyField; + MemoryField mValueField; uintptr_t mMapAddress; - size_t mMapKeyTypeSize; uint8_t mMapAlignment; }; } // namespace S2Plugin diff --git a/include/Views/ViewStdVector.h b/include/Views/ViewStdVector.h index 8d00aec..2a6afe7 100644 --- a/include/Views/ViewStdVector.h +++ b/include/Views/ViewStdVector.h @@ -1,5 +1,6 @@ #pragma once +#include "Configuration.h" #include "QtHelpers/AbstractContainerView.h" #include #include @@ -17,8 +18,7 @@ namespace S2Plugin void reloadContainer() override; private: - std::string mVectorType; + MemoryField mValueField; uintptr_t mVectorAddress; - size_t mVectorTypeSize; }; } // namespace S2Plugin diff --git a/include/Views/ViewStruct.h b/include/Views/ViewStruct.h index cffeada..4773636 100644 --- a/include/Views/ViewStruct.h +++ b/include/Views/ViewStruct.h @@ -1,5 +1,6 @@ #pragma once +#include "Configuration.h" #include #include #include @@ -8,6 +9,7 @@ namespace S2Plugin { class TreeViewMemoryFields; + class WidgetPagination; struct MemoryField; class ViewStruct : public QWidget @@ -25,12 +27,29 @@ namespace S2Plugin class ViewArray : public ViewStruct { + Q_OBJECT public: ViewArray(uintptr_t address, std::string arrayTypeName, size_t num, std::string name, QWidget* parent = nullptr); + protected slots: + void pageListUpdate(); + + private: + WidgetPagination* mPagination; + MemoryField mArray; + uintptr_t mArrayAddress; }; class ViewMatrix : public ViewStruct { + Q_OBJECT public: ViewMatrix(uintptr_t address, std::string arrayTypeName, size_t row, size_t col, std::string name, QWidget* parent = nullptr); + + protected slots: + void pageListUpdate(); + + private: + WidgetPagination* mPagination; + MemoryField mMatrix; + uintptr_t mMatrixAddress; }; } // namespace S2Plugin diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 24c04af..984feda 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -153,6 +153,7 @@ namespace S2Plugin //{MemoryFieldType::EntitySubclass, "", "", "", 0}, //{MemoryFieldType::DefaultStructType, "", "", "", 0}, {MemoryFieldType::Flag, "Flag", "", "", 0, false}, + {MemoryFieldType::Dummy, " ", "", "", 0, false}, }; } // namespace S2Plugin diff --git a/src/QtHelpers/AbstractContainerView.cpp b/src/QtHelpers/AbstractContainerView.cpp index ae1aa1a..688d8b1 100644 --- a/src/QtHelpers/AbstractContainerView.cpp +++ b/src/QtHelpers/AbstractContainerView.cpp @@ -4,6 +4,7 @@ #include "Data/EntityList.h" #include "QtHelpers/TreeViewMemoryFields.h" #include "QtHelpers/WidgetAutorefresh.h" +#include "QtHelpers/WidgetPagination.h" #include "QtPlugin.h" #include #include @@ -28,6 +29,11 @@ S2Plugin::AbstractContainerView::AbstractContainerView(QWidget* parent) : QWidge mMainTreeView = new TreeViewMemoryFields(this); QObject::connect(autoRefresh, &WidgetAutorefresh::refresh, mMainTreeView, static_cast(&TreeViewMemoryFields::updateTree)); mainLayout->addWidget(mMainTreeView); + + mPagination = new WidgetPagination(this); + mainLayout->addWidget(mPagination); + QObject::connect(mPagination, &WidgetPagination::pageUpdate, this, &AbstractContainerView::reloadContainer); + autoRefresh->toggleAutoRefresh(true); } diff --git a/src/QtHelpers/AbstractDatabaseView.cpp b/src/QtHelpers/AbstractDatabaseView.cpp index 46bce7c..123370d 100644 --- a/src/QtHelpers/AbstractDatabaseView.cpp +++ b/src/QtHelpers/AbstractDatabaseView.cpp @@ -77,19 +77,20 @@ S2Plugin::AbstractDatabaseView::AbstractDatabaseView(MemoryFieldType type, QWidg qobject_cast(tabLookup->layout())->addLayout(topLayout); mMainTreeView = new TreeViewMemoryFields(this); - mMainTreeView->setEnableChangeHighlighting(false); - mMainTreeView->addMemoryFields(config->typeFields(type), std::string(config->getTypeDisplayName(type)), 0); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &AbstractDatabaseView::fieldUpdated); - QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &AbstractDatabaseView::fieldExpanded); tabLookup->layout()->addWidget(mMainTreeView); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); + mMainTreeView->updateTableHeader(false); + mMainTreeView->setEnableChangeHighlighting(false); mMainTreeView->setColumnWidth(gsColField, 125); mMainTreeView->setColumnWidth(gsColValue, 250); mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainTreeView->updateTableHeader(); + + mMainTreeView->addMemoryFields(config->typeFields(type), std::string(config->getTypeDisplayName(type)), 0); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::memoryFieldValueUpdated, this, &AbstractDatabaseView::fieldUpdated); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::expanded, this, &AbstractDatabaseView::fieldExpanded); } // COMPARE diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index db7be40..47b950f 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -49,27 +49,30 @@ S2Plugin::TreeViewMemoryFields::TreeViewMemoryFields(QWidget* parent) : QTreeVie setDragEnabled(true); setAcceptDrops(true); - setStyleSheet("QTreeView::branch:has-siblings:!adjoins-item {\ - border-image: url(:/images/vline.png) 0;\ -}\ -QTreeView::branch:has-siblings:adjoins-item {\ - border-image: url(:/images/branch-more.png) 0;\ -}\ -QTreeView::branch:!has-children:!has-siblings:adjoins-item {\ - border-image: url(:/images/branch-end.png) 0;\ -}\ -QTreeView::branch:has-children:!has-siblings:closed,\ -QTreeView::branch:closed:has-children:has-siblings {\ - border-image: none;\ - image: url(:/images/branch-closed.png);\ -}\ -QTreeView::branch:open:has-children:!has-siblings,\ -QTreeView::branch:open:has-children:has-siblings {\ - border-image: none;\ - image: url(:/images/branch-open.png);\ -}"); + setStyleSheet(R"( +QTreeView::branch:has-siblings:!adjoins-item { + border-image: url(:/images/vline.png) 0; +} +QTreeView::branch:has-siblings:adjoins-item { + border-image: url(:/images/branch-more.png) 0; +} +QTreeView::branch:!has-children:!has-siblings:adjoins-item { + border-image: url(:/images/branch-end.png) 0; +} +QTreeView::branch:has-children:!has-siblings:closed, +QTreeView::branch:closed:has-children:has-siblings { + border-image: none; + image: url(:/images/branch-closed.png); +} +QTreeView::branch:open:has-children:!has-siblings, +QTreeView::branch:open:has-children:has-siblings { + border-image: none; + image: url(:/images/branch-open.png); +})"); QObject::connect(this, &QTreeView::clicked, this, &TreeViewMemoryFields::cellClicked); + // QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + // setFont(font); } void S2Plugin::TreeViewMemoryFields::addMemoryFields(const std::vector& fields, const std::string& mainName, uintptr_t structAddr, size_t initialDelta, uint8_t deltaPrefixCount, @@ -159,6 +162,8 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& typeName += "StdSet"; else if (field.type == MemoryFieldType::StdUnorderedMap && field.secondParameterType.empty()) // exception typeName += "StdUnorderedSet"; + // else if (field.type == MemoryFieldType::Dummy) + // typeName = ""; else if (auto str = Configuration::getTypeDisplayName(field.type); !str.empty()) typeName += QString::fromUtf8(str.data(), static_cast(str.size())); else @@ -256,11 +261,11 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& flags = 8; auto flagsParent = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); + MemoryField flagField; + flagField.type = MemoryFieldType::Flag; for (uint8_t x = 1; x <= flags; ++x) { - MemoryField flagField; flagField.name = "flag_" + std::to_string(x); - flagField.type = MemoryFieldType::Flag; bool showDelta = false; if ((x - 1) % 8 == 0) { @@ -335,8 +340,15 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& if (field.isPointer) delta = 0; + size_t idx = 0; + if (field.secondParameterType == "#") + { + delta += field.columns * index.get_size(); + memoryAddress += field.columns * index.get_size(); + idx = field.columns; + } - for (size_t idx = 0; idx < field.numberOfElements; ++idx) + for (; idx < field.numberOfElements; ++idx) { index.name.erase(initialNameSize); index.name += std::to_string(idx) + ']'; @@ -351,7 +363,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& } case MemoryFieldType::Matrix: { - if (field.secondParameterType == "$") + if (!field.secondParameterType.empty()) returnField = parent; else { @@ -361,21 +373,30 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& returnField->setData(field.columns, gsRoleColumns); } - if (field.rows <= 30 || field.secondParameterType == "$") // TODO: get the number from settings when done - // columns limit dealt by the array + if (field.rows <= 30 || !field.secondParameterType.empty()) // TODO: get the number from settings when done + // columns limit dealt by the array { - if (field.isPointer) - delta = 0; - MemoryField row; row.numberOfElements = field.columns; row.firstParameterType = field.firstParameterType; - row.secondParameterType = field.secondParameterType; + row.secondParameterType = "$"; // just to let it know it should put all the elements in, no matrix element + // it can't be # since we still want the array to be placed normally, just no size limit row.type = MemoryFieldType::Array; row.name = field.name + '['; auto initialNameSize = row.name.size(); - for (size_t idx = 0; idx < field.rows; ++idx) + if (field.isPointer) + delta = 0; + size_t idx = 0; + if (!field.secondParameterType.empty()) + { + auto rowStart = std::stoll(field.secondParameterType); + delta += rowStart * row.get_size(); + memoryAddress += rowStart * row.get_size(); + idx = rowStart; + } + + for (; idx < field.rows; ++idx) { row.name.erase(initialNameSize); row.name += std::to_string(idx) + ']'; @@ -413,7 +434,7 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& void S2Plugin::TreeViewMemoryFields::updateTableHeader(bool restoreColumnWidths) { - mModel->setHorizontalHeaderLabels({"Field", "Value", "Value (hex)", "Comparison value", "Comparison value (hex)", "Memory offset", "Δ", "Type", "Comment"}); + mModel->setHorizontalHeaderLabels({"Field", "Value", "Value (hex)", "Comparison value", "Comparison value (hex)", "Memory Address", "Δ", "Type", "Comment"}); setColumnHidden(gsColField, !activeColumns.test(gsColField)); setColumnHidden(gsColValue, !activeColumns.test(gsColValue)); @@ -2213,14 +2234,6 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } case MemoryFieldType::Dummy: { - if (isExpanded(itemField->index())) - itemValue->setData("[Collapse]", Qt::DisplayRole); - else - itemValue->setData("[Expand]", Qt::DisplayRole); - - // if (comparisonActive) - // itemComparisonValue->setData(itemValue->data(Qt::DisplayRole), Qt::DisplayRole); - // probably won't be involved in comparisons if (shouldUpdateChildren) { for (int x = 0; x < itemField->rowCount(); ++x) @@ -2578,7 +2591,6 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } case MemoryFieldType::DefaultStructType: case MemoryFieldType::EntitySubclass: - case MemoryFieldType::Dummy: { auto fieldIndex = index.sibling(index.row(), gsColField); if (isExpanded(fieldIndex)) @@ -2694,6 +2706,8 @@ void S2Plugin::TreeViewMemoryFields::clear() mSavedColumnWidths[gsColField] = columnWidth(gsColField); mSavedColumnWidths[gsColValue] = columnWidth(gsColValue); mSavedColumnWidths[gsColValueHex] = columnWidth(gsColValueHex); + mSavedColumnWidths[gsColComparisonValue] = columnWidth(gsColComparisonValue); + mSavedColumnWidths[gsColComparisonValueHex] = columnWidth(gsColComparisonValueHex); mSavedColumnWidths[gsColMemoryAddress] = columnWidth(gsColMemoryAddress); mSavedColumnWidths[gsColMemoryAddressDelta] = columnWidth(gsColMemoryAddressDelta); mSavedColumnWidths[gsColType] = columnWidth(gsColType); @@ -3109,3 +3123,25 @@ void S2Plugin::TreeViewMemoryFields::expandLast() if (rows != 0) expand(mod->index(mod->rowCount() - 1, 0)); } + +void S2Plugin::TreeViewMemoryFields::drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const +{ + static auto branchMore = QPixmap(":/images/branch-more.png"); + static auto branchEnd = []() + { + auto pixmap = QPixmap(":/images/branch-end.png"); + return pixmap.scaled(24, 17, Qt::AspectRatioMode::IgnoreAspectRatio, Qt::TransformationMode::SmoothTransformation); + }(); + + if (!drawTopBranch && index.parent() == QModelIndex()) + { + bool isLastChild = (index.row() == mModel->rowCount(index.parent()) - 1); + if (isLastChild) + painter->drawPixmap(rect.left() - 2, rect.top(), branchEnd); + else + painter->drawPixmap(rect.left() - 2, rect.top(), branchMore); + return; + } + + QTreeView::drawBranches(painter, rect, index); +} diff --git a/src/Views/ViewEntities.cpp b/src/Views/ViewEntities.cpp index 3c29f9f..3363d19 100644 --- a/src/Views/ViewEntities.cpp +++ b/src/Views/ViewEntities.cpp @@ -73,6 +73,11 @@ S2Plugin::ViewEntities::ViewEntities(QWidget* parent) : QWidget(parent) mMainTreeView = new TreeViewMemoryFields(this); mMainTreeView->setEnableChangeHighlighting(false); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress).disable(gsColComment); + mMainTreeView->updateTableHeader(false); + mMainTreeView->setColumnWidth(gsColField, 145); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 250); mainLayout->addWidget(mMainTreeView); @@ -92,6 +97,9 @@ void S2Plugin::ViewEntities::refreshEntities() } size_t entitiesShown = 0; + MemoryField field; + field.type = MemoryFieldType::EntityPointer; + field.isPointer = true; auto AddEntity = [&](size_t entity_ptr) { auto entity = Entity{Script::Memory::ReadQword(entity_ptr)}; @@ -103,10 +111,7 @@ void S2Plugin::ViewEntities::refreshEntities() return; } - MemoryField field; field.name = "entity_uid_" + std::to_string(entity.uid()); - field.type = MemoryFieldType::EntityPointer; - field.isPointer = true; mMainTreeView->addMemoryField(field, {}, entity_ptr, 0); ++entitiesShown; }; @@ -191,10 +196,6 @@ void S2Plugin::ViewEntities::refreshEntities() } setWindowTitle(QString("%1 Entities").arg(entitiesShown)); mMainTreeView->updateTableHeader(); - mMainTreeView->setColumnWidth(gsColField, 145); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(); } diff --git a/src/Views/ViewEntity.cpp b/src/Views/ViewEntity.cpp index 0e10118..1ab63b9 100644 --- a/src/Views/ViewEntity.cpp +++ b/src/Views/ViewEntity.cpp @@ -104,14 +104,14 @@ void S2Plugin::ViewEntity::initializeUI() // TAB FIELDS { + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); + mMainTreeView->updateTableHeader(false); mMainTreeView->setColumnWidth(gsColValue, 250); mMainTreeView->setColumnWidth(gsColField, 175); mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainTreeView->updateTableHeader(); QObject::connect(mMainTreeView, &TreeViewMemoryFields::offsetDropped, this, &ViewEntity::entityOffsetDropped); } // TAB MEMORY @@ -239,14 +239,14 @@ void S2Plugin::ViewEntity::interpretAsChanged(const QString& classType) } }; + MemoryField headerField; // potentially not safe if used get_size() since it won't update + headerField.type = MemoryFieldType::EntitySubclass; for (auto it = hierarchy.rbegin(); it != hierarchy.rend(); ++it, ++colorIndex) { if (colorIndex > colors.size()) colorIndex = 0; - MemoryField headerField; headerField.name = "" + *it + ""; - headerField.type = MemoryFieldType::EntitySubclass; headerField.jsonName = *it; mMainTreeView->addMemoryField(headerField, *it, mEntityPtr + delta, delta); // highlights fields in memory view, also updates delta diff --git a/src/Views/ViewEntityList.cpp b/src/Views/ViewEntityList.cpp index b887a55..00645b6 100644 --- a/src/Views/ViewEntityList.cpp +++ b/src/Views/ViewEntityList.cpp @@ -1,14 +1,23 @@ #include "Views/ViewEntityList.h" -#include "Configuration.h" #include "Data/EntityList.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetPagination.h" #include S2Plugin::ViewEntityList::ViewEntityList(uintptr_t address, QWidget* parent) : mEntityListAddress(address), AbstractContainerView(parent) { setWindowTitle("EntityList"); + mEntityField.isPointer = true; + mEntityField.type = MemoryFieldType::EntityPointer; + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment); //.disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress); + mMainTreeView->updateTableHeader(false); + mMainTreeView->setColumnWidth(gsColField, 120); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 200); reloadContainer(); } @@ -18,22 +27,19 @@ void S2Plugin::ViewEntityList::reloadContainer() EntityList entityList{mEntityListAddress}; + mPagination->setSize(entityList.size()); + if (entityList.size() == 0) + return; + auto entities = entityList.entities(); auto uids = entityList.getAllUids(); - for (size_t idx = 0; idx < entityList.size(); ++idx) + auto range = mPagination->getRange(); + for (size_t idx = range.first; idx < range.second; ++idx) { - MemoryField entityField; - entityField.name = "uid_" + std::to_string(uids[idx]); - entityField.isPointer = true; - entityField.type = MemoryFieldType::EntityPointer; - mMainTreeView->addMemoryField(entityField, {}, entities + idx * sizeof(uintptr_t), idx * sizeof(uintptr_t)); + mEntityField.name = "uid_" + std::to_string(uids[idx]); + mMainTreeView->addMemoryField(mEntityField, {}, entities + idx * sizeof(uintptr_t), idx * sizeof(uintptr_t)); } mMainTreeView->updateTableHeader(); - mMainTreeView->setColumnWidth(gsColField, 145); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(0, 0, true); } diff --git a/src/Views/ViewJournalPage.cpp b/src/Views/ViewJournalPage.cpp index 655f914..61c6e9d 100644 --- a/src/Views/ViewJournalPage.cpp +++ b/src/Views/ViewJournalPage.cpp @@ -40,17 +40,17 @@ S2Plugin::ViewJournalPage::ViewJournalPage(uintptr_t address, QWidget* parent) : refreshLayout->addWidget(interpretAsComboBox); mMainTreeView = new TreeViewMemoryFields(this); - mMainTreeView->addMemoryFields(config->typeFieldsOfDefaultStruct("JournalPage"), "JournalPage", mPageAddress); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); mainLayout->addWidget(mMainTreeView); - + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); + mMainTreeView->updateTableHeader(false); mMainTreeView->setColumnWidth(gsColValue, 250); mMainTreeView->setColumnWidth(gsColField, 125); mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->updateTableHeader(); + + mMainTreeView->addMemoryFields(config->typeFieldsOfDefaultStruct("JournalPage"), "JournalPage", mPageAddress); mMainTreeView->updateTree(0, 0, true); autoRefresh->toggleAutoRefresh(true); } @@ -101,7 +101,6 @@ void S2Plugin::ViewJournalPage::interpretAsChanged(const QString& text) mMainTreeView->addMemoryFields(structs, pageType, mPageAddress); } mMainTreeView->expandLast(); - mMainTreeView->setColumnWidth(gsColValue, 250); mMainTreeView->updateTableHeader(); mMainTreeView->updateTree(0, 0, true); } diff --git a/src/Views/ViewLevelGen.cpp b/src/Views/ViewLevelGen.cpp index 48c2aab..564b526 100644 --- a/src/Views/ViewLevelGen.cpp +++ b/src/Views/ViewLevelGen.cpp @@ -46,16 +46,15 @@ S2Plugin::ViewLevelGen::ViewLevelGen(uintptr_t address, QWidget* parent) : QWidg // TAB DATA { - mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::LevelGen), "LevelGen", mLevelGenPtr); - - mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->setColumnWidth(gsColField, 125); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); + mMainTreeView->updateTableHeader(false); + mMainTreeView->setColumnWidth(gsColValue, 160); + mMainTreeView->setColumnWidth(gsColField, 200); mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); - mMainTreeView->updateTableHeader(); + mMainTreeView->addMemoryFields(Configuration::get()->typeFields(MemoryFieldType::LevelGen), "LevelGen", mLevelGenPtr); QObject::connect(mMainTreeView, &TreeViewMemoryFields::levelGenRoomsPointerClicked, this, &ViewLevelGen::levelGenRoomsPointerClicked); } diff --git a/src/Views/ViewStdList.cpp b/src/Views/ViewStdList.cpp index be020e5..6a8b459 100644 --- a/src/Views/ViewStdList.cpp +++ b/src/Views/ViewStdList.cpp @@ -1,73 +1,90 @@ #include "Views/ViewStdList.h" -#include "Configuration.h" #include "Data/StdList.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetPagination.h" #include "pluginmain.h" #include -S2Plugin::ViewStdList::ViewStdList(uintptr_t addr, const std::string& valueType, bool oldType, QWidget* parent) - : mListType(valueType), mListAddress(addr), mOldType(oldType), AbstractContainerView(parent) +S2Plugin::ViewStdList::ViewStdList(uintptr_t addr, const std::string& valueType, bool oldType, QWidget* parent) : mListAddress(addr), mOldType(oldType), AbstractContainerView(parent) { - setWindowTitle(QString("std::list<%1>").arg(QString::fromStdString(mListType))); + mValueField = Configuration::get()->nameToMemoryField(valueType); + setWindowTitle(QString("std::list<%1>").arg(QString::fromStdString(valueType))); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment).disable(gsColMemoryAddressDelta); + mMainTreeView->updateTableHeader(false); + mMainTreeView->setColumnWidth(gsColField, 145); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 300); reloadContainer(); } void S2Plugin::ViewStdList::reloadContainer() { - auto config = Configuration::get(); mMainTreeView->clear(); - MemoryField field; - if (config->isPermanentPointer(mListType)) - { - field.type = MemoryFieldType::DefaultStructType; - field.jsonName = mListType; - field.isPointer = true; - } - else if (config->isJsonStruct(mListType)) - { - field.type = MemoryFieldType::DefaultStructType; - field.jsonName = mListType; - } - else if (auto type = config->getBuiltInType(mListType); type != MemoryFieldType::None) - { - field.type = type; - if (Configuration::isPointerType(type)) - field.isPointer = true; - } - else + if (mValueField.type == MemoryFieldType::None) { - dprintf("unknown type in ViewStdList::refreshVectorContents() (%s)\n", mListType.c_str()); + mPagination->setSize(0); return; } + if (mOldType) { OldStdList theList{mListAddress}; + if (!theList.isValid()) + { + mPagination->setSize(0); + return; + } + // had to make up some solution for pagination since the old list does not have an easy way to get the size + size_t perPage = mPagination->recordsPerPage(); + auto currentPageIndex = mPagination->getCurrentPage() - 1; + size_t thisPageEnd = currentPageIndex * perPage + perPage; size_t x = 0; - for (auto val_addr : theList) + auto cur = theList.begin(); + + for (; x < thisPageEnd && cur != theList.end(); ++x, ++cur) { - field.name = "val_" + std::to_string(x++); - mMainTreeView->addMemoryField(field, field.name, val_addr, 0); + if (x < currentPageIndex * perPage) + continue; + + mValueField.name = "val_" + std::to_string(x); + mMainTreeView->addMemoryField(mValueField, mValueField.name, cur.value_ptr(), 0); } + if (cur != theList.end()) + mPagination->setSize(x + perPage - 1); // to add at least one more page + else + mPagination->setSize(x - 1); } else { StdList theList{mListAddress}; - size_t x = 0; - for (auto val_addr : theList) + if (!Script::Memory::IsValidPtr(theList.end().address())) { - field.name = "val_" + std::to_string(x++); - mMainTreeView->addMemoryField(field, field.name, val_addr, 0); + mPagination->setSize(0); + return; + } + + mPagination->setSize(theList.size()); + if (theList.size() == 0) + return; + + auto range = mPagination->getRange(); + auto cur = theList.begin(); + auto end = theList.end(); + + for (size_t x = 0; x < range.second && cur != end; ++x, ++cur) + { + if (x < range.first) + continue; + + mValueField.name = "val_" + std::to_string(x); + mMainTreeView->addMemoryField(mValueField, mValueField.name, cur.value_ptr(), 0); } } mMainTreeView->updateTableHeader(); - mMainTreeView->setColumnWidth(gsColField, 145); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(0, 0, true); } diff --git a/src/Views/ViewStdMap.cpp b/src/Views/ViewStdMap.cpp index 94210d4..13238db 100644 --- a/src/Views/ViewStdMap.cpp +++ b/src/Views/ViewStdMap.cpp @@ -1,77 +1,99 @@ #include "Views/ViewStdMap.h" -#include "Configuration.h" #include "Data/StdMap.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetPagination.h" #include "pluginmain.h" #include -S2Plugin::ViewStdMap::ViewStdMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName, QWidget* parent) - : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mMapAddress(address), AbstractContainerView(parent) +S2Plugin::ViewStdMap::ViewStdMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName, QWidget* parent) : mMapAddress(address), AbstractContainerView(parent) { auto config = Configuration::get(); - mMapKeyTypeSize = config->getTypeSize(keytypeName); - mMapValueTypeSize = config->getTypeSize(valuetypeName); + + mKeyField = config->nameToMemoryField(keytypeName); + mValueField = config->nameToMemoryField(valuetypeName); mMapKeyAlignment = config->getAlingment(keytypeName); - mMapValueAlignment = mMapValueTypeSize == 0 ? 0 : config->getAlingment(valuetypeName); + mMapValueAlignment = valuetypeName.empty() ? 0 : config->getAlingment(valuetypeName); - if (mMapValueTypeSize == 0) + if (valuetypeName.empty()) setWindowTitle(QString("std::set<%1>").arg(QString::fromStdString(keytypeName))); else setWindowTitle(QString("std::map<%1, %2>").arg(QString::fromStdString(keytypeName), QString::fromStdString(valuetypeName))); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColComment); + mMainTreeView->updateTableHeader(false); + mMainTreeView->setEnableTopBranchDrawing(false); + mMainTreeView->setColumnWidth(gsColField, 145); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 250); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::collapsed, this, &ViewStdMap::onItemCollapsed); reloadContainer(); } void S2Plugin::ViewStdMap::reloadContainer() { + mMainTreeView->clear(); + if (mKeyField.type == MemoryFieldType::None) + { + mPagination->setSize(0); + return; + } if (!Script::Memory::IsValidPtr(Script::Memory::ReadQword(mMapAddress))) + { + mPagination->setSize(0); return; + } - StdMap the_map{mMapAddress, mMapKeyAlignment, mMapValueAlignment, mMapKeyTypeSize}; - auto config = Configuration::get(); - mMainTreeView->clear(); + StdMap the_map{mMapAddress, mMapKeyAlignment, mMapValueAlignment, mKeyField.get_size()}; + mPagination->setSize(the_map.size()); - MemoryField key_field = config->nameToMemoryField(mMapKeyType); - key_field.name = "key"; - if (key_field.type == MemoryFieldType::None) + if (the_map.size() == 0) return; - MemoryField value_field; - if (mMapValueTypeSize != 0) // if not StdSet - { - value_field = config->nameToMemoryField(mMapValueType); - value_field.name = "value"; - if (value_field.type == MemoryFieldType::None) - return; - } + auto range = mPagination->getRange(); + + mKeyField.name = "key"; + if (mKeyField.type == MemoryFieldType::None) + return; + + if (mValueField.type != MemoryFieldType::None) // if not StdSet + mValueField.name = "value"; auto _end = the_map.end(); auto _cur = the_map.begin(); MemoryField parent_field; parent_field.type = MemoryFieldType::Dummy; - for (int x = 0; _cur != _end && x < 300; ++x, ++_cur) + for (int x = 0; _cur != _end && x < range.second; ++x, ++_cur) { - if (mMapValueTypeSize == 0) // StdSet + if (x < range.first) + continue; + + if (mValueField.type == MemoryFieldType::None) // StdSet { - key_field.name = "key_" + std::to_string(x); - mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0); + mKeyField.name = "key_" + std::to_string(x); + mMainTreeView->addMemoryField(mKeyField, mKeyField.name, _cur.key_ptr(), 0); } else // StdMap { parent_field.name = "obj_" + std::to_string(x); QStandardItem* parent = mMainTreeView->addMemoryField(parent_field, parent_field.name, 0, 0); - mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0, 0, parent); - mMainTreeView->addMemoryField(value_field, value_field.name, _cur.value_ptr(), 0, 0, parent); + mMainTreeView->addMemoryField(mKeyField, mKeyField.name, _cur.key_ptr(), 0, 0, parent); + mMainTreeView->addMemoryField(mValueField, mValueField.name, _cur.value_ptr(), 0, 0, parent); + mMainTreeView->setExpanded(parent->index(), true); } } mMainTreeView->updateTableHeader(); - mMainTreeView->setColumnWidth(gsColField, 145); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(0, 0, true); } + +void S2Plugin::ViewStdMap::onItemCollapsed(const QModelIndex& index) +{ + if (index.parent() == QModelIndex()) + { + // prevent it from being collapsed + mMainTreeView->setExpanded(index, true); + } +} diff --git a/src/Views/ViewStdUnorderedMap.cpp b/src/Views/ViewStdUnorderedMap.cpp index 76cb004..e94cb3f 100644 --- a/src/Views/ViewStdUnorderedMap.cpp +++ b/src/Views/ViewStdUnorderedMap.cpp @@ -1,78 +1,99 @@ #include "Views/ViewStdUnorderedMap.h" -#include "Configuration.h" #include "Data/StdUnorderedMap.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetPagination.h" #include "pluginmain.h" #include S2Plugin::ViewStdUnorderedMap::ViewStdUnorderedMap(uintptr_t address, const std::string& keytypeName, const std::string& valuetypeName, QWidget* parent) - : mMapKeyType(keytypeName), mMapValueType(valuetypeName), mMapAddress(address), AbstractContainerView(parent) + : mMapAddress(address), AbstractContainerView(parent) { auto config = Configuration::get(); - mMapKeyTypeSize = config->getTypeSize(mMapKeyType); - if (mMapValueType.empty()) - mMapAlignment = config->getAlingment(mMapKeyType); + mKeyField = config->nameToMemoryField(keytypeName); + mValueField = config->nameToMemoryField(valuetypeName); + + if (valuetypeName.empty()) + mMapAlignment = config->getAlingment(mKeyField); else - mMapAlignment = std::max(config->getAlingment(mMapKeyType), config->getAlingment(mMapValueType)); + mMapAlignment = std::max(config->getAlingment(mKeyField), config->getAlingment(mValueField)); - if (mMapValueType.empty()) - setWindowTitle(QString("std::unordered_set<%1>").arg(QString::fromStdString(mMapKeyType))); + if (valuetypeName.empty()) + setWindowTitle(QString("std::unordered_set<%1>").arg(QString::fromStdString(keytypeName))); else - setWindowTitle(QString("std::unordered_map<%1, %2>").arg(QString::fromStdString(mMapKeyType), QString::fromStdString(mMapValueType))); + setWindowTitle(QString("std::unordered_map<%1, %2>").arg(QString::fromStdString(keytypeName), QString::fromStdString(valuetypeName))); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColMemoryAddressDelta).disable(gsColComment); + mMainTreeView->updateTableHeader(false); + mMainTreeView->setEnableTopBranchDrawing(false); + mMainTreeView->setColumnWidth(gsColField, 145); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 250); + QObject::connect(mMainTreeView, &TreeViewMemoryFields::collapsed, this, &ViewStdUnorderedMap::onItemCollapsed); reloadContainer(); } void S2Plugin::ViewStdUnorderedMap::reloadContainer() { + mMainTreeView->clear(); + if (mKeyField.type == MemoryFieldType::None) + { + mPagination->setSize(0); + return; // TODO display error + } if (!Script::Memory::IsValidPtr(Script::Memory::ReadQword(mMapAddress + sizeof(uintptr_t)))) - return; + { + mPagination->setSize(0); + return; // TODO display error + } - auto config = Configuration::get(); - mMainTreeView->clear(); + StdUnorderedMap the_map{mMapAddress, mKeyField.get_size(), mMapAlignment}; + mPagination->setSize(the_map.size()); - MemoryField key_field = config->nameToMemoryField(mMapKeyType); - key_field.name = "key"; - if (key_field.type == MemoryFieldType::None) + if (the_map.size() == 0) return; - MemoryField value_field; - if (!mMapValueType.empty()) // if not StdSet - { - value_field = config->nameToMemoryField(mMapValueType); - value_field.name = "value"; - if (value_field.type == MemoryFieldType::None) - return; - } + auto range = mPagination->getRange(); + + mKeyField.name = "key"; + if (mValueField.type != MemoryFieldType::None) // if not StdSet + mValueField.name = "value"; - StdUnorderedMap the_map{mMapAddress, mMapKeyTypeSize, mMapAlignment}; auto _end = the_map.end(); auto _cur = the_map.begin(); MemoryField parent_field; parent_field.type = MemoryFieldType::Dummy; - for (int x = 0; _cur != _end && x < 300; ++x, ++_cur) + for (int x = 0; _cur != _end && x < range.second; ++x, ++_cur) { - if (mMapValueType.empty()) // StdSet + if (x < range.first) + continue; + + if (mValueField.type == MemoryFieldType::None) // StdSet { - key_field.name = "key_" + std::to_string(x); - mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0); + mKeyField.name = "key_" + std::to_string(x); + mMainTreeView->addMemoryField(mKeyField, mKeyField.name, _cur.key_ptr(), 0); } else // StdMap { parent_field.name = "obj_" + std::to_string(x); QStandardItem* parent = mMainTreeView->addMemoryField(parent_field, parent_field.name, 0, 0); - mMainTreeView->addMemoryField(key_field, key_field.name, _cur.key_ptr(), 0, 0, parent); - mMainTreeView->addMemoryField(value_field, value_field.name, _cur.value_ptr(), 0, 0, parent); + mMainTreeView->addMemoryField(mKeyField, mKeyField.name, _cur.key_ptr(), 0, 0, parent); + mMainTreeView->addMemoryField(mValueField, mValueField.name, _cur.value_ptr(), 0, 0, parent); + mMainTreeView->setExpanded(parent->index(), true); } } mMainTreeView->updateTableHeader(); - mMainTreeView->setColumnWidth(gsColField, 145); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(0, 0, true); } + +void S2Plugin::ViewStdUnorderedMap::onItemCollapsed(const QModelIndex& index) +{ + if (index.parent() == QModelIndex()) + { + // prevent it from being collapsed + mMainTreeView->setExpanded(index, true); + } +} diff --git a/src/Views/ViewStdVector.cpp b/src/Views/ViewStdVector.cpp index e0c55a7..2bb1949 100644 --- a/src/Views/ViewStdVector.cpp +++ b/src/Views/ViewStdVector.cpp @@ -1,72 +1,64 @@ #include "Views/ViewStdVector.h" -#include "Configuration.h" #include "QtHelpers/TreeViewMemoryFields.h" +#include "QtHelpers/WidgetPagination.h" #include "pluginmain.h" #include -S2Plugin::ViewStdVector::ViewStdVector(uintptr_t vectoraddr, const std::string& vectorType, QWidget* parent) : mVectorType(vectorType), mVectorAddress(vectoraddr), AbstractContainerView(parent) +S2Plugin::ViewStdVector::ViewStdVector(uintptr_t vectoraddr, const std::string& vectorType, QWidget* parent) : mVectorAddress(vectoraddr), AbstractContainerView(parent) { - mVectorTypeSize = Configuration::get()->getTypeSize(mVectorType); + mValueField = Configuration::get()->nameToMemoryField(vectorType); setWindowTitle(QString("std::vector<%1>").arg(QString::fromStdString(vectorType))); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment); + mMainTreeView->updateTableHeader(false); + mMainTreeView->setColumnWidth(gsColField, 145); + mMainTreeView->setColumnWidth(gsColValueHex, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); + mMainTreeView->setColumnWidth(gsColType, 100); + mMainTreeView->setColumnWidth(gsColValue, 300); reloadContainer(); } void S2Plugin::ViewStdVector::reloadContainer() { - auto config = Configuration::get(); mMainTreeView->clear(); + if (mValueField.type == MemoryFieldType::None) + { + mPagination->setSize(0); + return; // TODO display error + } uintptr_t vectorBegin = Script::Memory::ReadQword(mVectorAddress); uintptr_t vectorEnd = Script::Memory::ReadQword(mVectorAddress + sizeof(uintptr_t)); if (vectorBegin == vectorEnd) + { + mPagination->setSize(0); return; + } if (vectorBegin >= vectorEnd || !Script::Memory::IsValidPtr(vectorBegin) || !Script::Memory::IsValidPtr(vectorEnd)) - return; // TODO display error - - auto vectorItemCount = (vectorEnd - vectorBegin) / mVectorTypeSize; - if ((vectorEnd - vectorBegin) % mVectorTypeSize != 0) - return; // TODO display error - - // limit big vectors - vectorItemCount = std::min(300ull, vectorItemCount); - - MemoryField field; - if (config->isPermanentPointer(mVectorType)) - { - field.type = MemoryFieldType::DefaultStructType; - field.jsonName = mVectorType; - field.isPointer = true; - } - else if (config->isJsonStruct(mVectorType)) { - field.type = MemoryFieldType::DefaultStructType; - field.jsonName = mVectorType; + mPagination->setSize(0); + return; // TODO display error } - else if (auto type = config->getBuiltInType(mVectorType); type != MemoryFieldType::None) + auto vectorItemCount = (vectorEnd - vectorBegin) / mValueField.get_size(); + if ((vectorEnd - vectorBegin) % mValueField.get_size() != 0) { - field.type = type; - if (Configuration::isPointerType(type)) - field.isPointer = true; + mPagination->setSize(0); + return; // TODO display error } - else - { - dprintf("unknown type in ViewStdVector::refreshVectorContents() (%s)\n", mVectorType.c_str()); + mPagination->setSize(vectorItemCount); + if (vectorItemCount == 0) return; - } - for (auto x = 0; x < vectorItemCount; ++x) + + auto range = mPagination->getRange(); + + for (auto x = range.first; x < range.second; ++x) { - field.name = "obj_" + std::to_string(x); - mMainTreeView->addMemoryField(field, field.name, vectorBegin + x * mVectorTypeSize, x * mVectorTypeSize); + mValueField.name = "obj_" + std::to_string(x); + mMainTreeView->addMemoryField(mValueField, mValueField.name, vectorBegin + x * mValueField.get_size(), x * mValueField.get_size()); } mMainTreeView->updateTableHeader(); - mMainTreeView->setColumnWidth(gsColField, 145); - mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); - mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->setColumnWidth(gsColValue, 300); mMainTreeView->updateTree(0, 0, true); } diff --git a/src/Views/ViewStruct.cpp b/src/Views/ViewStruct.cpp index 10d737d..754270a 100644 --- a/src/Views/ViewStruct.cpp +++ b/src/Views/ViewStruct.cpp @@ -3,6 +3,7 @@ #include "Configuration.h" #include "QtHelpers/TreeViewMemoryFields.h" #include "QtHelpers/WidgetAutorefresh.h" +#include "QtHelpers/WidgetPagination.h" #include "QtPlugin.h" #include #include @@ -31,43 +32,72 @@ S2Plugin::ViewStruct::ViewStruct(uintptr_t address, const std::vectoraddWidget(labelButton); mainLayout->addWidget(mMainTreeView); - mMainTreeView->addMemoryFields(fields, name, address); mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex); + mMainTreeView->updateTableHeader(false); mMainTreeView->setColumnWidth(gsColValue, 250); - mMainTreeView->setColumnWidth(gsColField, 125); + mMainTreeView->setColumnWidth(gsColField, 200); mMainTreeView->setColumnWidth(gsColValueHex, 125); - mMainTreeView->setColumnWidth(gsColMemoryAddress, 125); + mMainTreeView->setColumnWidth(gsColMemoryAddress, 120); mMainTreeView->setColumnWidth(gsColMemoryAddressDelta, 75); mMainTreeView->setColumnWidth(gsColType, 100); - mMainTreeView->updateTableHeader(); + mMainTreeView->addMemoryFields(fields, name, address); mMainTreeView->updateTree(0, 0, true); autoRefresh->toggleAutoRefresh(true); } S2Plugin::ViewArray::ViewArray(uintptr_t address, std::string arrayTypeName, size_t num, std::string name, QWidget* parent) - : ViewStruct(0, {}, arrayTypeName + " " + name + '[' + std::to_string(num) + ']', parent) + : ViewStruct(0, {}, arrayTypeName + " " + name + '[' + std::to_string(num) + ']', parent), mArrayAddress(address) +{ + + mArray.name = name; + mArray.type = MemoryFieldType::Array; + mArray.firstParameterType = arrayTypeName; + mArray.secondParameterType = '#'; // just to let it know it should put all the elements in, no array element + mArray.numberOfElements = num; + + mPagination = new WidgetPagination(this); + layout()->addWidget(mPagination); + QObject::connect(mPagination, &WidgetPagination::pageUpdate, this, &ViewArray::pageListUpdate); + mPagination->setSize(num); + pageListUpdate(); +} + +void S2Plugin::ViewArray::pageListUpdate() { - MemoryField array; - array.name = name; - array.type = MemoryFieldType::Array; - array.firstParameterType = arrayTypeName; - array.secondParameterType = '#'; // just to let it know it should put all the elements in, no array element - array.numberOfElements = num; - mMainTreeView->addMemoryField(array, {}, address, 0); + mMainTreeView->clear(); + mMainTreeView->updateTableHeader(); + auto range = mPagination->getRange(); + // using columns to store the initial index + mArray.columns = range.first; + mArray.numberOfElements = range.second; + mMainTreeView->addMemoryField(mArray, {}, mArrayAddress, 0); } S2Plugin::ViewMatrix::ViewMatrix(uintptr_t address, std::string arrayTypeName, size_t rows, size_t columns, std::string name, QWidget* parent) - : ViewStruct(0, {}, arrayTypeName + " " + name + '[' + std::to_string(rows) + "][" + std::to_string(columns) + ']', parent) + : ViewStruct(0, {}, arrayTypeName + " " + name + '[' + std::to_string(rows) + "][" + std::to_string(columns) + ']', parent), mMatrixAddress(address) { - MemoryField matrix; - matrix.name = name; - matrix.type = MemoryFieldType::Matrix; - matrix.firstParameterType = arrayTypeName; - matrix.secondParameterType = '$'; // just to let it know it should put all the elements in, no matrix element - // it can't be # since we still want the array to be placed normally, just no size limit - matrix.rows = rows; - matrix.columns = columns; - mMainTreeView->addMemoryField(matrix, {}, address, 0); + mMatrix.name = name; + mMatrix.type = MemoryFieldType::Matrix; + mMatrix.firstParameterType = arrayTypeName; + mMatrix.rows = rows; + mMatrix.columns = columns; + + mPagination = new WidgetPagination(this); + layout()->addWidget(mPagination); + QObject::connect(mPagination, &WidgetPagination::pageUpdate, this, &ViewMatrix::pageListUpdate); + mPagination->setSize(rows); + pageListUpdate(); +} + +void S2Plugin::ViewMatrix::pageListUpdate() +{ + mMainTreeView->clear(); + mMainTreeView->updateTableHeader(); + auto range = mPagination->getRange(); + // dirty way to transfer the first index + mMatrix.secondParameterType = std::to_string(range.first); + mMatrix.rows = range.second; + mMainTreeView->addMemoryField(mMatrix, {}, mMatrixAddress, 0); } QSize S2Plugin::ViewStruct::sizeHint() const From d79bdf6aa3e75fd1e703c8ca6445a44b7a9d53b4 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sun, 16 Jun 2024 22:17:35 +0200 Subject: [PATCH 15/20] some post self review changes --- include/Configuration.h | 1 - include/Data/EntityList.h | 205 +++++++++++++------------ include/QtHelpers/WidgetPagination.h | 1 + src/QtHelpers/TreeViewMemoryFields.cpp | 6 +- src/Views/ViewEntityList.cpp | 2 +- 5 files changed, 106 insertions(+), 109 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index dc30577..7821a28 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -180,7 +180,6 @@ namespace S2Plugin private: size_t size{0}; friend class Configuration; - friend class ViewMatrix; }; struct RoomCode diff --git a/include/Data/EntityList.h b/include/Data/EntityList.h index c82d042..03ffa77 100644 --- a/include/Data/EntityList.h +++ b/include/Data/EntityList.h @@ -33,108 +33,109 @@ namespace S2Plugin { return mSize; } - struct Iterator - { - // Iterator(){}; - explicit Iterator(const EntityList entityList, uint32_t index) noexcept : Iterator(entityList.begin()) - { - advance(index); - } - void advance(int count) noexcept // should probably be int64 - { - addr.first += count * sizeof(uintptr_t); - addr.second += count * sizeof(uint32_t); - } - Iterator& operator++() noexcept - { - advance(1); - return *this; - } - Iterator operator++(int) noexcept - { - auto copy = *this; - advance(1); - return copy; - } - Iterator& operator--() noexcept - { - advance(-1); - } - Iterator operator--(int) noexcept - { - auto copy = *this; - advance(-1); - return copy; - } - std::pair operator*() const - { - return {addr.first, Script::Memory::ReadDword(addr.second)}; - } - bool operator==(const Iterator& other) const noexcept - { - return addr.first == other.addr.first; - } - bool operator!=(const Iterator& other) const noexcept - { - return addr.first != other.addr.first; - } - uintptr_t entityRaw() const - { - return Script::Memory::ReadQword(addr.first); - } - Entity entity() const - { - return Entity{Script::Memory::ReadQword(addr.first)}; - } - uint32_t uid() const - { - return Script::Memory::ReadDword(addr.second); - } - - private: - explicit Iterator(uintptr_t entitiesAddress, uintptr_t uidsAddress) : addr(entitiesAddress, uidsAddress){}; - std::pair addr; - friend class EntityList; - }; - - Iterator begin() const - { - return Iterator{entities(), uids()}; - } - Iterator end() const - { - uintptr_t entitiesEnd = entities() + size() * sizeof(uintptr_t); - uintptr_t uidsEnd = uids() + size() * sizeof(uint32_t); - return Iterator{entitiesEnd, uidsEnd}; - } - const Iterator cbegin() const - { - return begin(); - } - const Iterator cend() const - { - return end(); - } - Iterator find(uint32_t uid) const - { - auto endIterator = end(); - for (auto it = begin(); it != endIterator; ++it) - { - if (it.uid() == uid) - return it; - } - return endIterator; - } - Iterator findEntity(uintptr_t addr) const - { - auto endIterator = end(); - for (auto it = begin(); it != endIterator; ++it) - { - if (it.entityRaw() == addr) - return it; - } - return endIterator; - } + // prefer getting whole struct as it's much faster then calling read multiple times + // struct Iterator + //{ + // // Iterator(){}; + // explicit Iterator(const EntityList entityList, uint32_t index) noexcept : Iterator(entityList.begin()) + // { + // advance(index); + // } + // void advance(int count) noexcept // should probably be int64 + // { + // addr.first += count * sizeof(uintptr_t); + // addr.second += count * sizeof(uint32_t); + // } + // Iterator& operator++() noexcept + // { + // advance(1); + // return *this; + // } + // Iterator operator++(int) noexcept + // { + // auto copy = *this; + // advance(1); + // return copy; + // } + // Iterator& operator--() noexcept + // { + // advance(-1); + // } + // Iterator operator--(int) noexcept + // { + // auto copy = *this; + // advance(-1); + // return copy; + // } + // std::pair operator*() const + // { + // return {addr.first, Script::Memory::ReadDword(addr.second)}; + // } + // bool operator==(const Iterator& other) const noexcept + // { + // return addr.first == other.addr.first; + // } + // bool operator!=(const Iterator& other) const noexcept + // { + // return addr.first != other.addr.first; + // } + // uintptr_t entityRaw() const + // { + // return Script::Memory::ReadQword(addr.first); + // } + // Entity entity() const + // { + // return Entity{Script::Memory::ReadQword(addr.first)}; + // } + // uint32_t uid() const + // { + // return Script::Memory::ReadDword(addr.second); + // } + // + // private: + // explicit Iterator(uintptr_t entitiesAddress, uintptr_t uidsAddress) : addr(entitiesAddress, uidsAddress){}; + // std::pair addr; + // friend class EntityList; + //}; + // + // Iterator begin() const + //{ + // return Iterator{entities(), uids()}; + // } + // Iterator end() const + //{ + // uintptr_t entitiesEnd = entities() + size() * sizeof(uintptr_t); + // uintptr_t uidsEnd = uids() + size() * sizeof(uint32_t); + // return Iterator{entitiesEnd, uidsEnd}; + // } + // const Iterator cbegin() const + //{ + // return begin(); + // } + // const Iterator cend() const + //{ + // return end(); + // } + // Iterator find(uint32_t uid) const + //{ + // auto endIterator = end(); + // for (auto it = begin(); it != endIterator; ++it) + // { + // if (it.uid() == uid) + // return it; + // } + // return endIterator; + // } + // Iterator findEntity(uintptr_t addr) const + //{ + // auto endIterator = end(); + // for (auto it = begin(); it != endIterator; ++it) + // { + // if (it.entityRaw() == addr) + // return it; + // } + // return endIterator; + // } std::vector getAllEntities() const { std::vector result; diff --git a/include/QtHelpers/WidgetPagination.h b/include/QtHelpers/WidgetPagination.h index 15e0dab..c1904bd 100644 --- a/include/QtHelpers/WidgetPagination.h +++ b/include/QtHelpers/WidgetPagination.h @@ -7,6 +7,7 @@ #include #include #include +#include #include namespace S2Plugin diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index 47b950f..b33d63d 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -71,8 +71,6 @@ QTreeView::branch:open:has-children:has-siblings { })"); QObject::connect(this, &QTreeView::clicked, this, &TreeViewMemoryFields::cellClicked); - // QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); - // setFont(font); } void S2Plugin::TreeViewMemoryFields::addMemoryFields(const std::vector& fields, const std::string& mainName, uintptr_t structAddr, size_t initialDelta, uint8_t deltaPrefixCount, @@ -162,8 +160,6 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& typeName += "StdSet"; else if (field.type == MemoryFieldType::StdUnorderedMap && field.secondParameterType.empty()) // exception typeName += "StdUnorderedSet"; - // else if (field.type == MemoryFieldType::Dummy) - // typeName = ""; else if (auto str = Configuration::getTypeDisplayName(field.type); !str.empty()) typeName += QString::fromUtf8(str.data(), static_cast(str.size())); else @@ -2155,7 +2151,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional { std::optional comparisonValue; auto addr = valueComparisonMemoryOffset == 0 ? 0 : valueComparisonMemoryOffset + 0x14; - comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, true, false, highlightColor); + comparisonValue = updateField(itemField, addr, itemComparisonValue, nullptr, nullptr, true, nullptr, false, false, highlightColor); if (comparisonValue.has_value()) { if (comparisonValue.value() == 0) diff --git a/src/Views/ViewEntityList.cpp b/src/Views/ViewEntityList.cpp index 00645b6..aed08ee 100644 --- a/src/Views/ViewEntityList.cpp +++ b/src/Views/ViewEntityList.cpp @@ -11,7 +11,7 @@ S2Plugin::ViewEntityList::ViewEntityList(uintptr_t address, QWidget* parent) : m mEntityField.isPointer = true; mEntityField.type = MemoryFieldType::EntityPointer; - mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment); //.disable(gsColMemoryAddressDelta).disable(gsColMemoryAddress); + mMainTreeView->activeColumns.disable(gsColComparisonValue).disable(gsColComparisonValueHex).disable(gsColComment); mMainTreeView->updateTableHeader(false); mMainTreeView->setColumnWidth(gsColField, 120); mMainTreeView->setColumnWidth(gsColValueHex, 125); From f8b6196036e2df5a586c686bd73e3ab1dde5ca6a Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Sat, 22 Jun 2024 15:47:22 +0200 Subject: [PATCH 16/20] re-arrange toolbar, add HUD and GameAPI view, fix mistake in edit value dialog --- include/Configuration.h | 2 + include/Spelunky2.h | 4 + include/Views/ViewToolbar.h | 2 + resources/Spelunky2.json | 250 ++++++++++++++++++++++++ src/Configuration.cpp | 2 + src/Data/Lookup.cpp | 57 ++++++ src/QtHelpers/DialogEditSimpleValue.cpp | 4 +- src/Views/ViewToolbar.cpp | 138 +++++++------ 8 files changed, 394 insertions(+), 65 deletions(-) diff --git a/include/Configuration.h b/include/Configuration.h index 7821a28..6d8e55f 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -89,6 +89,8 @@ namespace S2Plugin State32, // this is signed, can be negative! Skip, GameManager, + GameAPI, + Hud, State, SaveGame, LevelGen, diff --git a/include/Spelunky2.h b/include/Spelunky2.h index 66284de..2db8ad7 100644 --- a/include/Spelunky2.h +++ b/include/Spelunky2.h @@ -27,6 +27,8 @@ namespace S2Plugin uintptr_t get_GameManagerPtr(); uintptr_t get_SaveDataPtr(); uintptr_t get_OnlinePtr(); + uintptr_t get_GameAPIPtr(); + uintptr_t get_HudPtr(); uintptr_t get_StatePtr() const { return heapBaseAddr + GAME_OFFSET::STATE; @@ -67,6 +69,8 @@ namespace S2Plugin uintptr_t mGameManagerPtr{0}; uintptr_t mOnlinePtr{0}; + uintptr_t mGameAPIPtr{0}; + uintptr_t mHudPtr{0}; EntityDB mEntityDB; ParticleDB mParticleDB; diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index cd577fe..f4a189b 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -48,6 +48,8 @@ namespace S2Plugin void showLogger(); void showOnline(); void showThreads(); + void showGameAPI(); + void showHud(); private slots: void clearLabels(); diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 6448204..5787aef 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -8342,6 +8342,256 @@ } } } + ], + "GameAPI": [ + { "field": "unknown1", "type": "Bool" }, + { "field": "padding1", "type": "Skip", "offset": 7 }, + { "field": "unknown3", "type": "DataPointer", "comment": "input related" }, + { "field": "renderer", "type": "Renderer", "pointer": true }, + { "field": "window_width", "type": "UnsignedDword" }, + { "field": "window_height", "type": "UnsignedDword" }, + { "field": "unknown5", "type": "UnsignedQword", "comment": "always garbage?" }, + { "field": "exe_begin", "type": "DataPointer" }, + { "field": "unknown7", "type": "UnsignedQword", "comment": "some offset" }, + { "field": "unknown8", "type": "UnsignedQword", "comment": "always garbage?" }, + { "field": "SteamAPI_Callback", "type": "DataPointer" }, + { "field": "unknown10a", "type": "UnsignedByte", "comment": "bool?" }, + { "field": "padding2", "type": "Skip", "offset": 3 }, + { "field": "unknown10b", "type": "UnsignedDword" }, + { "field": "unknown11", "type": "UnsignedQword", "comment": "always garbage?" }, + { "field": "unknown12", "type": "UnsignedQword", "comment": "always garbage?" } + ], + "Renderer": [ + { + "field": "__vftable", + "type": "VirtualFunctionTable", + "functions": { + "0": { + "name": "~Renderer", + "params": "", + "return": "" + }, + "1": { + "name": "some_dx_stuff", + "params": "", + "return": "" + } + } + }, + { "field": "render_width", "type": "UnsignedDword", "comment": "same as window size unless resolution scale is set" }, + { "field": "render_height", "type": "UnsignedDword", "comment": "same as window size unless resolution scale is set" }, + { "field": "fps", "type": "UnsignedDword", "comment": "changing it doesn't seam to do anything" }, + { "field": "fps_denominator", "type": "UnsignedDword" }, + { "field": "render_width2", "type": "UnsignedDword", "comment": "repeat?" }, + { "field": "render_height2", "type": "UnsignedDword" }, + { "field": "flags1", "type": "Flags8" }, + { "field": "flags2", "type": "Flags8" }, + { "field": "padding1", "type": "Skip", "offset": 6 }, + { "field": "unknown1", "type": "Array", "length": 67, "arraytype": "DataPointer" }, + { "field": "unknown2", "type": "UnsignedDword" }, + { "field": "unknown3", "type": "UnsignedDword" }, + { "field": "unknown4", "type": "UnsignedDword" }, + { "field": "unknown5", "type": "UnsignedDword" }, + { "field": "unknown6", "type": "Array", "length": 512, "arraytype": "DataPointer" }, + { "field": "skip1", "type": "Skip", "offset": 520264, "comment": "a lot of nothing" }, + { "field": "unknown38", "type": "UnsignedQword", "comment": "bool?" }, + { "field": "unknown39", "type": "Float", "comment": "not sure if actually float" }, + { "field": "unknown40", "type": "Float" }, + { "field": "unknown41", "type": "Float" }, + { "field": "unknown42", "type": "UnsignedDword" }, + { "field": "unknown43a", "type": "ConstCharPointerPointer" }, + { "field": "unknown43b", "type": "ConstCharPointerPointer" }, + { "field": "unknown43c", "type": "ConstCharPointerPointer" }, + { "field": "unknown44a", "type": "UnsignedQword" }, + { "field": "unknown44b", "type": "UnsignedQword" }, + { "field": "unknown44c", "type": "UnsignedQword" }, + { "field": "unknown44d", "type": "UnsignedQword" }, + { "field": "unknown45", "type": "UnsignedQword", "comment": "bool?" }, + { "field": "unknown46", "type": "DataPointer", "comment": "feels like two standard containers or something?" }, + { "field": "unknown47", "type": "DataPointer" }, + { "field": "unknown48", "type": "UnsignedQword" }, + { "field": "unknown49", "type": "DataPointer" }, + { "field": "unknown50", "type": "DataPointer" }, + { "field": "unknown51", "type": "UnsignedQword" }, + { "field": "unknown52", "type": "UnsignedDword" }, + { "field": "unknown53", "type": "UnsignedDword", "comment": "padding probably" }, + { "field": "unknown54", "type": "DataPointer", "comment": "sometimes -1 sometimes pointer?" }, + { "field": "unknown55", "type": "Dword" }, + { "field": "unknown56", "type": "UnsignedDword" }, + { "field": "unknown57", "type": "UnsignedQword" }, + { "field": "unknown58", "type": "Qword" }, + { "field": "unknown59", "type": "UnsignedWord" }, + { "field": "unknown60", "type": "UnsignedWord" }, + { "field": "unknown60b", "type": "UnsignedDword", "comment": "padding?" }, + { "field": "unknown61a", "type": "DataPointer" }, + { "field": "unknown61b", "type": "DataPointer" }, + { "field": "unknown61c", "type": "DataPointer" }, + { "field": "unknown61d", "type": "DataPointer" }, + { "field": "unknown62", "type": "UnsignedQword", "comment": "bool?" }, + { "field": "unknown63", "type": "StdUnorderedMap", "keytype": "UnsignedDword", "valuetype": "DataPointer" }, + { "field": "unknown64a", "type": "StdVector", "valuetype": "ConstCharPointerPointer" }, + { "field": "unknown64b", "type": "StdVector", "valuetype": "ConstCharPointerPointer" }, + { "field": "unknown65", "type": "StdVector", "valuetype": "ConstCharPointerPointer", "comment": "splash 0,1,2" }, + { "field": "unknown66", "type": "StdVector", "valuetype": "ConstCharPointerPointer", "comment": "fonts, basecamp, pet" }, + { "field": "unknown67", "type": "StdVector", "valuetype": "ConstCharPointerPointer", "comment": "fonts and menu textures + characters (character select screen textures?)" }, + { "field": "unknown68", "type": "StdVector", "valuetype": "ConstCharPointerPointer", "comment": "main menu background textures?" }, + { "field": "unknown69", "type": "StdVector", "valuetype": "ConstCharPointerPointer" }, + { "field": "unknown70", "type": "StdVector", "valuetype": "ConstCharPointerPointer", "comment": "menu textures?" }, + { "field": "unknown71", "type": "StdVector", "valuetype": "ConstCharPointerPointer", "comment": "only the ai.dds?" }, + { "field": "unknown72", "type": "StdVector", "valuetype": "ConstCharPointerPointer" }, + { "field": "unknown73", "type": "StdVector", "valuetype": "ConstCharPointerPointer" }, + { "field": "unknown74", "type": "UnsignedQword" }, + { "field": "unknown75", "type": "UnsignedQword" }, + { "field": "unknown80", "type": "UnsignedByte" }, + { "field": "unknown81", "type": "UnsignedByte" }, + { "field": "unknown82", "type": "UnsignedByte" }, + { "field": "unknown83", "type": "UnsignedByte", "comment": "padding probably" }, + { "field": "current_zoom", "type": "Float" }, + { "field": "target_zoom", "type": "Float" }, + { "field": "target_zoom_offset", "type": "Float" }, + { "field": "current_zoom_offset", "type": "Float" }, + { "field": "backlayer_light_level", "type": "Float", "comment": "constantly overwritten by theme virtual get_backlayer_light_level" }, + { "field": "unknown84", "type": "UnsignedByte" }, + { "field": "unknown85", "type": "UnsignedByte" }, + { "field": "padding_probably1", "type": "UnsignedWord" }, + { "field": "padding_probably2", "type": "UnsignedDword" }, + { "field": "unknown87", "type": "DataPointer" }, + { "field": "skip2", "type": "Skip", "offset": 2768, "comment": "probably some static arrays of ... stuff" }, + { "field": "swap_chain?", "type": "DataPointer" }, + { "field": "unknown88", "type": "DataPointer" }, + { "field": "unknown89", "type": "DataPointer" }, + { "field": "unknown90", "type": "DataPointer" }, + { "field": "unknown91", "type": "UnsignedQword" } + ], + "HudInventory": [ + { "field": "enabled", "type": "Bool" }, + { "field": "health", "type": "UnsignedByte" }, + { "field": "bombs", "type": "UnsignedByte" }, + { "field": "ropes", "type": "UnsignedByte" }, + { "field": "unknown1", "type": "UnsignedByte" }, + { "field": "ankh", "type": "Bool" }, + { "field": "kapala", "type": "Bool" }, + { "field": "unknown2", "type": "UnsignedByte" }, + { "field": "kapala_scale", "type": "Float" }, + { "field": "kapala_sprite", "type": "SpritePosition" }, + { "field": "poison", "type": "Bool" }, + { "field": "curse", "type": "Bool" }, + { "field": "elixir", "type": "Bool" }, + { "field": "unknown5", "type": "UnsignedByte" }, + { "field": "crown", "type": "EntityDBID" }, + { "field": "powerups_sprite_position", "type": "Array", "length": 18, "arraytype": "SpritePosition" }, + { "field": "powerups_count", "type": "UnsignedDword" } + ], + "HudElement": [ + { "field": "name_state", "type": "State32", "states": { "0": "highlight", "1": "normal" } }, + { "field": "opacity", "type": "Float" }, + { "field": "highlight_time", "type": "Dword", "comment": "when the player is highlighted it gets set to state.time_level + 0xB4, when this hits the time, it sets the name_state to normal" } + ], + "HudPlayer": [ + { "field": "hud_element", "type": "HudElement" }, + { "field": "health", "type": "Word" }, + { "field": "bombs", "type": "Word" }, + { "field": "ropes", "type": "Word" }, + { "field": "unknown1", "type": "Word", "comment": "padding probably" }, + { "field": "unknown2", "type": "Dword", "comment": "some weird padding? since all fields are 32bit max feels wierd to be here" } + ], + "HudMoney": [ + { "field": "hud_element", "type": "HudElement" }, + { "field": "total", "type": "Dword" }, + { "field": "counter", "type": "Dword" }, + { "field": "timer", "type": "UnsignedByte" }, + { "field": "padding1", "type": "UnsignedByte" }, + { "field": "padding2", "type": "UnsignedWord" } + ], + "Hud": [ + { "field": "player_inventory", "type": "Array", "length": 4, "arraytype": "HudInventory" }, + { "field": "udjat", "type": "Bool" }, + { "field": "unknown1", "type": "UnsignedByte" }, + { "field": "unknown2", "type": "UnsignedWord" }, + { "field": "money_total", "type": "Dword" }, + { "field": "money_counter", "type": "Dword" }, + { "field": "time_level", "type": "UnsignedDword", "comment": "in ms" }, + { "field": "time_total", "type": "UnsignedDword", "comment": "in ms" }, + { "field": "world_num", "type": "UnsignedByte" }, + { "field": "level_num", "type": "UnsignedByte" }, + { "field": "angry_shopkeeper", "type": "Bool" }, + { "field": "seed_shown", "type": "Bool" }, + { "field": "seed", "type": "UnsignedDword" }, + { "field": "opacity", "type": "Float" }, + { "field": "player_highlight", "type": "TextureRenderingInfo" }, + { "field": "player_hearth", "type": "TextureRenderingInfo" }, + { "field": "player_ankh", "type": "TextureRenderingInfo" }, + { "field": "kapala_icon", "type": "TextureRenderingInfo" }, + { "field": "player_crown", "type": "TextureRenderingInfo" }, + { "field": "unknown_texture5", "type": "TextureRenderingInfo" }, + { "field": "player_bomb", "type": "TextureRenderingInfo" }, + { "field": "player_rope", "type": "TextureRenderingInfo" }, + { "field": "unknown_texture8", "type": "TextureRenderingInfo" }, + { "field": "unknown_texture9", "type": "TextureRenderingInfo" }, + { "field": "unknown_texture10", "type": "TextureRenderingInfo" }, + { "field": "unknown_texture11", "type": "TextureRenderingInfo" }, + { "field": "unknown_texture12", "type": "TextureRenderingInfo" }, + { "field": "udjat_icon", "type": "TextureRenderingInfo" }, + { "field": "unknown_texture14", "type": "TextureRenderingInfo" }, + { "field": "money_and_time_highlight", "type": "TextureRenderingInfo" }, + { "field": "dollar_icon", "type": "TextureRenderingInfo" }, + { "field": "hourglass_icon", "type": "TextureRenderingInfo" }, + { "field": "clover_icon", "type": "TextureRenderingInfo" }, + { "field": "level_highlight", "type": "TextureRenderingInfo" }, + { "field": "level_icon", "type": "TextureRenderingInfo", "comment": "inco, seeded incon, angry shopkeeper etc." }, + { "field": "seed_background", "type": "TextureRenderingInfo" }, + { "field": "roll_in", "type": "Float" }, + { "field": "unknown6", "type": "Float" }, + { "field": "unknown7", "type": "Float" }, + { "field": "unknown8", "type": "Float" }, + { "field": "unknown9", "type": "Float" }, + { "field": "unknown10", "type": "Float" }, + { "field": "player_zoom", "type": "Array", "length": 4, "arraytype": "Float" }, + { "field": "unknown15", "type": "Float" }, + { "field": "unknown16", "type": "Float" }, + { "field": "unknown17", "type": "Float" }, + { "field": "unknown18", "type": "Float" }, + { "field": "players", "type": "Array", "length": 4, "arraytype": "HudPlayer" }, + { "field": "money", "type": "HudMoney" }, + { "field": "money_increase_sparkle", "type": "ParticleEmitterInfoPointer" }, + { "field": "timer", "type": "HudElement" }, + { "field": "level", "type": "HudElement" }, + { "field": "hud_elements?", "type": "StdVector", "valuetype": "DataPointer", "comment": "Just pointers to the hud elements" }, + { "field": "unknown20", "type": "UnsignedDword" }, + { "field": "unknown21", "type": "UnsignedByte" }, + { "field": "unknown22", "type": "UnsignedByte" }, + { "field": "unknown23", "type": "UnsignedWord" }, + { "field": "clover_falling_apart_timer", "type": "Float" }, + { "field": "unknown25", "type": "Float" }, + { "field": "unknown26", "type": "ParticleEmitterInfoPointer" }, + { "field": "unknown27", "type": "TextureRenderingInfo" }, + { "field": "unknown28", "type": "TextureRenderingInfo" }, + { "field": "unknown29", "type": "TextureRenderingInfo" }, + { "field": "unknown30", "type": "Float" }, + { "field": "unknown31", "type": "UnsignedDword", "comment": "padding?" }, + { "field": "unknown32", "type": "StdUnorderedMap", "keytype": "UnsignedDword", "valuetype": "UnsignedDword" }, + { "field": "unknown33", "type": "Float" }, + { "field": "unknown34", "type": "Float" }, + { "field": "unknown35", "type": "Float" }, + { "field": "unknown36", "type": "Float" }, + { "field": "unknown37", "type": "Float" }, + { "field": "unknown38", "type": "Float" }, + { "field": "unknown39", "type": "Float" }, + { "field": "loading_dragon", "type": "TextureRenderingInfo" }, + { "field": "loading_visibility", "type": "Float" }, + { "field": "unknown42", "type": "Float" }, + { "field": "unknown42b", "type": "Float" }, + { "field": "loading_cog", "type": "TextureRenderingInfo" }, + { "field": "unknown45", "type": "Float" }, + { "field": "loading_cog_timer", "type": "UnsignedDword" }, + { "field": "unknown47", "type": "UnsignedDword" }, + { "field": "unknown48", "type": "Bool" }, + { "field": "unknown49", "type": "UnsignedByte", "comment": "probably padding" }, + { "field": "unknown50", "type": "UnsignedWord", "comment": "probably padding" }, + { "field": "unknown51", "type": "Float" }, + { "field": "unknown52", "type": "UnsignedDword", "comment": "probably padding" }, + { "field": "player_cursed_paricles", "type": "Array", "length": 4, "arraytype": "ParticleEmitterInfoPointer" }, + { "field": "player_poisoned_paricles", "type": "Array", "length": 4, "arraytype": "ParticleEmitterInfoPointer" } ] } } diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 984feda..d470620 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -125,6 +125,8 @@ namespace S2Plugin {MemoryFieldType::TextureDB, "TextureDB", "", "TextureDB", 0, false}, {MemoryFieldType::CharacterDB, "CharacterDB", "", "CharacterDB", 0, false}, {MemoryFieldType::Online, "Online", "", "Online", 0, false}, + {MemoryFieldType::GameAPI, "GameAPI", "", "GameAPI", 0, false}, + {MemoryFieldType::Hud, "Hud", "", "Hud", 0, false}, // Special Types {MemoryFieldType::EntityPointer, "Entity pointer", "Entity*", "EntityPointer", 8, true}, {MemoryFieldType::EntityDBPointer, "EntityDB pointer", "EntityDB*", "EntityDBPointer", 8, true}, diff --git a/src/Data/Lookup.cpp b/src/Data/Lookup.cpp index 6b067b5..378244d 100644 --- a/src/Data/Lookup.cpp +++ b/src/Data/Lookup.cpp @@ -207,3 +207,60 @@ const S2Plugin::VirtualTableLookup& S2Plugin::Spelunky2::get_VirtualTableLookup( } return mVirtualTableLookup; } + +uintptr_t S2Plugin::Spelunky2::get_GameAPIPtr() +{ + if (mGameAPIPtr != 0) + return mGameAPIPtr; + + auto instructionAddress = Script::Pattern::FindMem(afterBundle, afterBundleSize, "C6 00 00 48 C7 40 18 00 00 00 00"); + if (instructionAddress == 0) + { + displayError("Lookup error: unable to find GameAPI (1)"); + return mGameAPIPtr; + } + instructionAddress = Script::Pattern::FindMem(instructionAddress - 0x30, 0x30, "48 8B 05"); + if (instructionAddress == 0) + { + displayError("Lookup error: unable to find GameAPI (2)"); + return mGameAPIPtr; + } + + auto relativeOffset = Script::Memory::ReadDword(instructionAddress + 3); + auto gameAPIPointer = instructionAddress + 7 + relativeOffset; + mGameAPIPtr = Script::Memory::ReadQword(gameAPIPointer); + if (!Script::Memory::IsValidPtr(mGameAPIPtr)) + { + displayError("Lookup error: GameAPI not yet initialized"); + mGameAPIPtr = 0; + } + return mGameAPIPtr; +} + +uintptr_t S2Plugin::Spelunky2::get_HudPtr() +{ + if (mHudPtr != 0) + return mHudPtr; + + auto instructionAddress = Script::Pattern::FindMem(afterBundle, afterBundleSize, "41 C6 47 6B 01"); + if (instructionAddress == 0) + { + displayError("Lookup error: unable to find Hud (1)"); + return mHudPtr; + } + instructionAddress = Script::Pattern::FindMem(instructionAddress + 5, 0x24, "48 8D 0D"); + if (instructionAddress == 0) + { + displayError("Lookup error: unable to find Hud (2)"); + return mHudPtr; + } + + auto relativeOffset = Script::Memory::ReadDword(instructionAddress + 3); + mHudPtr = instructionAddress + 7 + relativeOffset; + if (!Script::Memory::IsValidPtr(mHudPtr)) + { + displayError("Lookup error: unable to find Hud (3)"); + mHudPtr = 0; + } + return mHudPtr; +} diff --git a/src/QtHelpers/DialogEditSimpleValue.cpp b/src/QtHelpers/DialogEditSimpleValue.cpp index 6edf594..3167a8b 100644 --- a/src/QtHelpers/DialogEditSimpleValue.cpp +++ b/src/QtHelpers/DialogEditSimpleValue.cpp @@ -205,7 +205,7 @@ void S2Plugin::DialogEditSimpleValue::changeBtnClicked() if (obj) // probably not needed but just in case v = obj->value(); - Script::Memory::WriteByte(mMemoryAddress, static_cast(v)); + Script::Memory::WriteWord(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::Dword: @@ -215,7 +215,7 @@ void S2Plugin::DialogEditSimpleValue::changeBtnClicked() if (obj) // probably not needed but just in case v = obj->value(); - Script::Memory::WriteByte(mMemoryAddress, static_cast(v)); + Script::Memory::WriteDword(mMemoryAddress, static_cast(v)); break; } case MemoryFieldType::UnsignedDword: diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index a228e37..cebf24e 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -36,97 +36,87 @@ S2Plugin::ViewToolbar::ViewToolbar(QMdiArea* mdiArea, QWidget* parent) : QDockWi container->setLayout(mainLayout); setWidget(container); + auto addDivider = [&mainLayout]() + { + auto line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + mainLayout->addWidget(line); + }; + setTitleBarWidget(new QWidget(this)); - auto btnEntityDB = new QPushButton(this); - btnEntityDB->setText("Entity DB"); + mainLayout->addWidget(new QLabel("Databases:"), 0, Qt::AlignHCenter); + + auto btnEntityDB = new QPushButton("Entity DB", this); mainLayout->addWidget(btnEntityDB); QObject::connect(btnEntityDB, &QPushButton::clicked, this, &ViewToolbar::showEntityDB); - - auto btnTextureDB = new QPushButton(this); - btnTextureDB->setText("Texture DB"); + auto btnTextureDB = new QPushButton("Texture DB", this); mainLayout->addWidget(btnTextureDB); QObject::connect(btnTextureDB, &QPushButton::clicked, this, &ViewToolbar::showTextureDB); - - auto btnParticleDB = new QPushButton(this); - btnParticleDB->setText("Particle DB"); + auto btnParticleDB = new QPushButton("Particle DB", this); mainLayout->addWidget(btnParticleDB); QObject::connect(btnParticleDB, &QPushButton::clicked, this, &ViewToolbar::showParticleDB); - - auto btnStringsTable = new QPushButton(this); - btnStringsTable->setText("Strings DB"); + auto btnStringsTable = new QPushButton("Strings DB", this); mainLayout->addWidget(btnStringsTable); QObject::connect(btnStringsTable, &QPushButton::clicked, this, &ViewToolbar::showStringsTable); - - auto btnCharacterDB = new QPushButton(this); - btnCharacterDB->setText("Character DB"); + auto btnCharacterDB = new QPushButton("Character DB", this); mainLayout->addWidget(btnCharacterDB); QObject::connect(btnCharacterDB, &QPushButton::clicked, this, &ViewToolbar::showCharacterDB); - auto divider = new QFrame(this); - divider->setFrameShape(QFrame::HLine); - divider->setFrameShadow(QFrame::Sunken); - mainLayout->addWidget(divider); + addDivider(); + mainLayout->addWidget(new QLabel("Game structs:"), 0, Qt::AlignHCenter); - auto btnState = new QPushButton(this); - btnState->setText("State"); + auto btnGameManager = new QPushButton("GameManager", this); + mainLayout->addWidget(btnGameManager); + QObject::connect(btnGameManager, &QPushButton::clicked, this, &ViewToolbar::showGameManager); + auto btnOnline = new QPushButton("Online", this); + mainLayout->addWidget(btnOnline); + QObject::connect(btnOnline, &QPushButton::clicked, this, &ViewToolbar::showOnline); + auto btnVirtualTable = new QPushButton("Virtual Table", this); + mainLayout->addWidget(btnVirtualTable); + QObject::connect(btnVirtualTable, &QPushButton::clicked, this, &ViewToolbar::showVirtualTableLookup); + auto btnGameAPI = new QPushButton("Game API", this); + mainLayout->addWidget(btnGameAPI); + QObject::connect(btnGameAPI, &QPushButton::clicked, this, &ViewToolbar::showGameAPI); + auto btnHud = new QPushButton("Hud", this); + mainLayout->addWidget(btnHud); + QObject::connect(btnHud, &QPushButton::clicked, this, &ViewToolbar::showHud); + auto btnThreads = new QPushButton("Threads", this); + mainLayout->addWidget(btnThreads); + QObject::connect(btnThreads, &QPushButton::clicked, this, &ViewToolbar::showThreads); + + addDivider(); + mainLayout->addWidget(new QLabel("Main Thread heap:", this), 0, Qt::AlignHCenter); + + auto btnState = new QPushButton("State", this); mainLayout->addWidget(btnState); QObject::connect(btnState, &QPushButton::clicked, this, &ViewToolbar::showMainThreadState); - - auto btnEntities = new QPushButton(this); - btnEntities->setText("Entities"); + auto btnEntities = new QPushButton("Entities", this); + btnEntities->setToolTip("Lookup entities in current level"); mainLayout->addWidget(btnEntities); QObject::connect(btnEntities, &QPushButton::clicked, this, &ViewToolbar::showEntities); - - auto btnLevelGen = new QPushButton(this); - btnLevelGen->setText("LevelGen"); + auto btnLevelGen = new QPushButton("LevelGen", this); mainLayout->addWidget(btnLevelGen); QObject::connect(btnLevelGen, &QPushButton::clicked, this, &ViewToolbar::showMainThreadLevelGen); - - auto btnGameManager = new QPushButton(this); - btnGameManager->setText("GameManager"); - mainLayout->addWidget(btnGameManager); - QObject::connect(btnGameManager, &QPushButton::clicked, this, &ViewToolbar::showGameManager); - - auto btnSaveGame = new QPushButton(this); - btnSaveGame->setText("SaveGame"); + auto btnSaveGame = new QPushButton("SaveGame", this); mainLayout->addWidget(btnSaveGame); QObject::connect(btnSaveGame, &QPushButton::clicked, this, &ViewToolbar::showSaveGame); - auto btnOnline = new QPushButton(this); - btnOnline->setText("Online"); - mainLayout->addWidget(btnOnline); - QObject::connect(btnOnline, &QPushButton::clicked, this, &ViewToolbar::showOnline); - - auto btnVirtualTable = new QPushButton(this); - btnVirtualTable->setText("Virtual Table"); - mainLayout->addWidget(btnVirtualTable); - QObject::connect(btnVirtualTable, &QPushButton::clicked, this, &ViewToolbar::showVirtualTableLookup); - - auto divider2 = new QFrame(this); - divider2->setFrameShape(QFrame::HLine); - divider2->setFrameShadow(QFrame::Sunken); - mainLayout->addWidget(divider2); + mainLayout->addStretch(); + addDivider(); + mainLayout->addWidget(new QLabel("Tools:"), 0, Qt::AlignHCenter); - auto btnLogger = new QPushButton(this); - btnLogger->setText("Logger"); + auto btnLogger = new QPushButton("Logger", this); + btnLogger->setToolTip("Log data over time"); mainLayout->addWidget(btnLogger); QObject::connect(btnLogger, &QPushButton::clicked, this, &ViewToolbar::showLogger); - - auto btnThreads = new QPushButton(this); - btnThreads->setText("Threads"); - mainLayout->addWidget(btnThreads); - QObject::connect(btnThreads, &QPushButton::clicked, this, &ViewToolbar::showThreads); - - mainLayout->addStretch(); - - auto btnClearLabels = new QPushButton(this); - btnClearLabels->setText("Clear labels"); + auto btnClearLabels = new QPushButton("Clear labels", this); + btnClearLabels->setToolTip("Clear all labels crated by the plugin (auto labels)"); mainLayout->addWidget(btnClearLabels); QObject::connect(btnClearLabels, &QPushButton::clicked, this, &ViewToolbar::clearLabels); - - auto btnReloadConfig = new QPushButton(this); - btnReloadConfig->setText("Reload JSON"); + auto btnReloadConfig = new QPushButton("Reload JSON", this); + btnReloadConfig->setToolTip("Reload config from Spelunky2.json and Spelunky2Entities.json"); mainLayout->addWidget(btnReloadConfig); QObject::connect(btnReloadConfig, &QPushButton::clicked, this, &ViewToolbar::reloadConfig); } @@ -371,6 +361,28 @@ void S2Plugin::ViewToolbar::showSaveGame() } } +void S2Plugin::ViewToolbar::showGameAPI() +{ + if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_GameAPIPtr() != 0) + { + auto w = new ViewStruct(Spelunky2::get()->get_GameAPIPtr(), Configuration::get()->typeFields(MemoryFieldType::GameAPI), "GameAPI"); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); + } +} + +void S2Plugin::ViewToolbar::showHud() +{ + if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_HudPtr() != 0) + { + auto w = new ViewStruct(Spelunky2::get()->get_HudPtr(), Configuration::get()->typeFields(MemoryFieldType::Hud), "Hud"); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); + } +} + void S2Plugin::ViewToolbar::showLogger() { auto w = new ViewLogger(); From bee776cb0bed09ad1732581d3123526307efa64e Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Tue, 25 Jun 2024 21:07:31 +0200 Subject: [PATCH 17/20] add OnHeapPointer, EntityFactory, separate LiquidPhysics to it's own window, some more GameAPI stuff --- CMakeLists.txt | 1 + include/Configuration.h | 4 ++ include/Spelunky2.h | 13 ++++ include/Views/ViewEntityFactory.h | 34 +++++++++ include/Views/ViewToolbar.h | 4 +- resources/Spelunky2.json | 99 ++++++++++++++++++++------ src/Configuration.cpp | 13 ++++ src/QtHelpers/TreeViewMemoryFields.cpp | 81 +++++++++++++++++++++ src/Views/ViewEntityDB.cpp | 4 +- src/Views/ViewToolbar.cpp | 36 ++++++++++ 10 files changed, 264 insertions(+), 25 deletions(-) create mode 100644 include/Views/ViewEntityFactory.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 65c4fc4..331a578 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -83,6 +83,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Views/ViewStdUnorderedMap.h include/Views/ViewStdList.h include/Views/ViewEntityList.h + include/Views/ViewEntityFactory.h include/QtHelpers/StyledItemDelegateHTML.h include/QtHelpers/StyledItemDelegateColorPicker.h include/QtHelpers/TreeViewMemoryFields.h diff --git a/include/Configuration.h b/include/Configuration.h index 6d8e55f..2f9e8eb 100644 --- a/include/Configuration.h +++ b/include/Configuration.h @@ -70,6 +70,7 @@ namespace S2Plugin Dummy, // dummy type for uses like fake parent type in StdMap CodePointer, DataPointer, + OnHeapPointer, Byte, UnsignedByte, Word, @@ -91,9 +92,12 @@ namespace S2Plugin GameManager, GameAPI, Hud, + EntityFactory, State, SaveGame, LevelGen, + LiquidPhysics, + LiquidPhysicsPointer, EntityDB, EntityPointer, EntityDBPointer, diff --git a/include/Spelunky2.h b/include/Spelunky2.h index 2db8ad7..acfe8f7 100644 --- a/include/Spelunky2.h +++ b/include/Spelunky2.h @@ -31,12 +31,25 @@ namespace S2Plugin uintptr_t get_HudPtr(); uintptr_t get_StatePtr() const { + if (heapBaseAddr == 0) + return 0; + return heapBaseAddr + GAME_OFFSET::STATE; }; uintptr_t get_LevelGenPtr() const { + if (heapBaseAddr == 0) + return 0; + return heapBaseAddr + GAME_OFFSET::LEVEL_GEN; }; + uintptr_t get_LiquidEnginePtr() const + { + if (heapBaseAddr == 0) + return 0; + + return heapBaseAddr + GAME_OFFSET::LIQUID_ENGINE; + } uintptr_t get_HeapBase() const { return heapBaseAddr; diff --git a/include/Views/ViewEntityFactory.h b/include/Views/ViewEntityFactory.h new file mode 100644 index 0000000..ba82cd0 --- /dev/null +++ b/include/Views/ViewEntityFactory.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Configuration.h" +#include "QtHelpers/TreeViewMemoryFields.h" +#include "Spelunky2.h" +#include "ViewStruct.h" +#include +#include + +namespace S2Plugin +{ + class ViewEntityFactory : public ViewStruct + { + public: + explicit ViewEntityFactory(QWidget* parent = nullptr) : ViewStruct(0, {}, "Entity Factory", parent) + { + auto config = Configuration::get(); + MemoryField dummy; + dummy.type = MemoryFieldType::Dummy; + dummy.name = "EntityDB here"; + auto& entityDB = Spelunky2::get()->get_EntityDB(); + auto address = entityDB.addressOfIndex(0); + mMainTreeView->addMemoryField(dummy, {}, address, 0); + auto offset = entityDB.entitySize() * (config->entityList().highestID() + 2); // +2 for the id 0 and one extra slot at the end + mMainTreeView->addMemoryFields(config->typeFields(MemoryFieldType::EntityFactory), "EntityFactory", address + offset, offset); + } + + protected: + QSize sizeHint() const override + { + return QSize(750, 200); + } + }; +} // namespace S2Plugin diff --git a/include/Views/ViewToolbar.h b/include/Views/ViewToolbar.h index f4a189b..463a952 100644 --- a/include/Views/ViewToolbar.h +++ b/include/Views/ViewToolbar.h @@ -27,6 +27,7 @@ namespace S2Plugin void showVirtualFunctions(uintptr_t address, const std::string& typeName); void showJournalPage(uintptr_t address); void showLevelGen(uintptr_t address); + void showLiquidPhysics(uintptr_t address); void showArray(uintptr_t address, std::string name, std::string arrayTypeName, size_t length); void showMatrix(uintptr_t address, std::string name, std::string arrayTypeName, size_t rows, size_t columns); void showEntityList(uintptr_t address); @@ -38,10 +39,10 @@ namespace S2Plugin ViewTextureDB* showTextureDB(); void showStringsTable(); ViewCharacterDB* showCharacterDB(); - void showMainThreadState(); void showGameManager(); void showMainThreadLevelGen(); + void showMainThreadLiquidPhysics(); void showEntities(); ViewVirtualTable* showVirtualTableLookup(); void showSaveGame(); @@ -50,6 +51,7 @@ namespace S2Plugin void showThreads(); void showGameAPI(); void showHud(); + void showEntityFactory(); private slots: void clearLabels(); diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index 5787aef..a771812 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -1807,7 +1807,7 @@ { "field": "stickers", "type": "Array", "length":20, "arraytype": "TextureRenderingInfo" } ], "SaveGameDataPointer": [ - { "field": "heap_offset", "type": "UnsignedQword" }, + { "field": "heap_offset", "type": "OnHeapPointer" }, { "field": "journal_popup_ui", "type": "JournalPopupUI" } ], "ScreenBase": [ @@ -4084,8 +4084,10 @@ "type": "EntityUID", "comment": "if set to -1, you have free control over camera focus through focus_x, focus_y" }, - { "field": "unknown3", "type": "UnsignedDword" }, - { "field": "unknown4", "type": "UnsignedDword" }, + { "field": "peek_layer_timer", "type": "UnsignedDword" }, + { "field": "peek_layer", "type": "UnsignedByte" }, + { "field": "unknown4b", "type": "UnsignedByte" }, + { "field": "unknown4c", "type": "UnsignedWord" }, { "field": "inertia", "type": "Float", @@ -4101,7 +4103,7 @@ { "field": "bottom", "type": "Float" }, { "field": "top", "type": "Float" } ], - "LiquidPhysicsPointer": [ + "LiquidPhysics": [ { "field": "unknown1", "type": "MysteryLiquidPointer1" }, { "field": "water_physics_defaults", "type": "LiquidPhysicsParams" }, { "field": "water_physics_engine", "type": "LiquidPhysicsEngine" }, @@ -7439,11 +7441,7 @@ { "field": "unknown44", "type": "ConstCharPointerPointer" }, { "field": "texture_num", "type": "UnsignedDword" }, { "field": "padding_probably1", "type": "UnsignedDword" }, - { - "field": "entity_offset", - "type": "UnsignedQword", - "comment": "the offset of the associated entity in memory, starting from the memory segment that State resides in" - }, + { "field": "entity_offset", "type": "OnHeapPointer" }, { "field": "flip_horizontal", "type": "Bool", "comment": "facing left" }, { "field": "padding_probably2", "type": "UnsignedByte" }, { "field": "padding_probably3", "type": "UnsignedByte" }, @@ -8343,6 +8341,46 @@ } } ], + "UnknownSteamStuff": [ + { "field": "unknown1", "type": "ConstCharPointer" }, + { "field": "unknown2", "type": "Dword" }, + { "field": "unknown3", "type": "Dword" }, + { "field": "unknown4", "type": "CodePointer" } + ], + "SteamCallback": [ + { + "field": "__vftable", + "type": "VirtualFunctionTable", + "functions": { + "0": { + "name": "unknown", + "params": "", + "return": "" + }, + "1": { + "name": "unknown", + "params": "", + "return": "" + }, + "2": { + "name": "unknown", + "params": "", + "return": "" + }, + "3": { + "name": "~SteamCallback", + "params": "", + "return": "" + } + }}, + { "field": "unknown1", "type": "UnsignedByte", "comment": "bool?" }, + { "field": "padding2", "type": "Skip", "offset": 7, "comment": "probably padding, if it's subclass below" }, + { "field": "steam_overlay_open", "type": "Bool" }, + { "field": "unknown4", "type": "Skip", "offset": 3, "comment": "probably padding" }, + { "field": "unknown_timer", "type": "UnsignedDword", "comment": "can run only when game is out of focus" }, + { "field": "unknown6", "type": "Float" }, + { "field": "unknown7", "type": "UnsignedDword", "comment": "probably padding" } + ], "GameAPI": [ { "field": "unknown1", "type": "Bool" }, { "field": "padding1", "type": "Skip", "offset": 7 }, @@ -8350,16 +8388,12 @@ { "field": "renderer", "type": "Renderer", "pointer": true }, { "field": "window_width", "type": "UnsignedDword" }, { "field": "window_height", "type": "UnsignedDword" }, - { "field": "unknown5", "type": "UnsignedQword", "comment": "always garbage?" }, + { "field": "unknown5", "type": "UnknownSteamStuff", "pointer": true }, { "field": "exe_begin", "type": "DataPointer" }, - { "field": "unknown7", "type": "UnsignedQword", "comment": "some offset" }, - { "field": "unknown8", "type": "UnsignedQword", "comment": "always garbage?" }, - { "field": "SteamAPI_Callback", "type": "DataPointer" }, - { "field": "unknown10a", "type": "UnsignedByte", "comment": "bool?" }, - { "field": "padding2", "type": "Skip", "offset": 3 }, - { "field": "unknown10b", "type": "UnsignedDword" }, - { "field": "unknown11", "type": "UnsignedQword", "comment": "always garbage?" }, - { "field": "unknown12", "type": "UnsignedQword", "comment": "always garbage?" } + { "field": "unknown7", "type": "OnHeapPointer", "comment": "some offset, OnHeapPointer for now" }, + { "field": "unknown8", "type": "CodePointer", "comment": "steam related" }, + { "field": "SteamAPI_Callback", "type": "SteamCallback", "comment": "OnGameOverlayActivated" }, + { "field": "unknown14", "type": "UnsignedQword" } ], "Renderer": [ { @@ -8479,8 +8513,8 @@ { "field": "elixir", "type": "Bool" }, { "field": "unknown5", "type": "UnsignedByte" }, { "field": "crown", "type": "EntityDBID" }, - { "field": "powerups_sprite_position", "type": "Array", "length": 18, "arraytype": "SpritePosition" }, - { "field": "powerups_count", "type": "UnsignedDword" } + { "field": "powerup_sprites", "type": "Array", "length": 18, "arraytype": "SpritePosition" }, + { "field": "powerup_count", "type": "UnsignedDword" } ], "HudElement": [ { "field": "name_state", "type": "State32", "states": { "0": "highlight", "1": "normal" } }, @@ -8519,7 +8553,7 @@ { "field": "seed", "type": "UnsignedDword" }, { "field": "opacity", "type": "Float" }, { "field": "player_highlight", "type": "TextureRenderingInfo" }, - { "field": "player_hearth", "type": "TextureRenderingInfo" }, + { "field": "player_heart", "type": "TextureRenderingInfo" }, { "field": "player_ankh", "type": "TextureRenderingInfo" }, { "field": "kapala_icon", "type": "TextureRenderingInfo" }, { "field": "player_crown", "type": "TextureRenderingInfo" }, @@ -8580,7 +8614,7 @@ { "field": "loading_dragon", "type": "TextureRenderingInfo" }, { "field": "loading_visibility", "type": "Float" }, { "field": "unknown42", "type": "Float" }, - { "field": "unknown42b", "type": "Float" }, + { "field": "unknown43", "type": "Float" }, { "field": "loading_cog", "type": "TextureRenderingInfo" }, { "field": "unknown45", "type": "Float" }, { "field": "loading_cog_timer", "type": "UnsignedDword" }, @@ -8592,6 +8626,27 @@ { "field": "unknown52", "type": "UnsignedDword", "comment": "probably padding" }, { "field": "player_cursed_paricles", "type": "Array", "length": 4, "arraytype": "ParticleEmitterInfoPointer" }, { "field": "player_poisoned_paricles", "type": "Array", "length": 4, "arraytype": "ParticleEmitterInfoPointer" } + ], + "EntityPool": [ + { "field": "slot_size", "type": "UnsignedDword" }, + { "field": "initial_slots", "type": "UnsignedDword" }, + { "field": "slots_growth", "type": "UnsignedDword" }, + { "field": "current_slots", "type": "UnsignedDword" }, + { "field": "unknwon1", "type": "UnsignedQword" }, + { "field": "unknwon2", "type": "StdVector", "valuetype": "EntityPointer", "pointer": true }, + { "field": "empty_buckets", "type": "StdVector", "valuetype": "DataPointer", "pointer": true } + ], + "EntityPoolOnHeapPointer": [ + { "field": "pointer", "type": "OnHeapPointer", "pointertype": "EntityPool" } + ], + "EntityFactory": [ + // EntityDB will be inserted here automatically + { "field": "active_types", "type": "Array", "length": 917, "arraytype": "Bool" }, + { "field": "skip1", "type": "Skip", "offset": 3 }, + { "field": "entity_buckets", "type": "StdUnorderedMap", "keytype": "UnsignedDword", "valuetype": "EntityPoolOnHeapPointer", "comment": "key is the bucket size" }, + { "field": "entity_names_map", "type": "StdUnorderedMap", "keytype": "StdString", "valuetype": "UnsignedWord" }, + { "field": "unknwon2", "type": "UnsignedQword" }, + { "field": "unknwon3", "type": "UnsignedQword" } ] } } diff --git a/src/Configuration.cpp b/src/Configuration.cpp index d470620..bb05257 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -127,7 +127,10 @@ namespace S2Plugin {MemoryFieldType::Online, "Online", "", "Online", 0, false}, {MemoryFieldType::GameAPI, "GameAPI", "", "GameAPI", 0, false}, {MemoryFieldType::Hud, "Hud", "", "Hud", 0, false}, + {MemoryFieldType::EntityFactory, "EntityFactory", "", "EntityFactory", 0, false}, + {MemoryFieldType::LiquidPhysics, "LiquidPhysics", "", "LiquidPhysics", 0, false}, // Special Types + {MemoryFieldType::OnHeapPointer, "OnHeap Pointer", "OnHeapPointer", "OnHeapPointer", 8, false}, // not pointer since it's more of a offset {MemoryFieldType::EntityPointer, "Entity pointer", "Entity*", "EntityPointer", 8, true}, {MemoryFieldType::EntityDBPointer, "EntityDB pointer", "EntityDB*", "EntityDBPointer", 8, true}, {MemoryFieldType::EntityDBID, "EntityDB ID", "uint32_t", "EntityDBID", 4, false}, @@ -142,6 +145,7 @@ namespace S2Plugin {MemoryFieldType::COThemeInfoPointer, "COThemeInfoPointer", "ThemeInfo*", "COThemeInfoPointer", 8, true}, // just theme name {MemoryFieldType::LevelGenRoomsPointer, "LevelGenRoomsPointer", "LevelGenRooms*", "LevelGenRoomsPointer", 8, true}, {MemoryFieldType::LevelGenRoomsMetaPointer, "LevelGenRoomsMetaPointer", "LevelGenRoomsMeta*", "LevelGenRoomsMetaPointer", 8, true}, + {MemoryFieldType::LiquidPhysicsPointer, "LiquidPhysicsPointer", "LiquidPhysicsPointer*", "LiquidPhysicsPointer", 8, true}, {MemoryFieldType::JournalPagePointer, "JournalPagePointer", "JournalPage*", "JournalPagePointer", 8, true}, {MemoryFieldType::LevelGenPointer, "LevelGenPointer", "LevelGen*", "LevelGenPointer", 8, true}, {MemoryFieldType::StringsTableID, "StringsTable ID", "uint32_t", "StringsTableID", 4, false}, @@ -540,6 +544,14 @@ S2Plugin::MemoryField S2Plugin::Configuration::populateMemoryField(const nlohman throw std::runtime_error("Missing `col` parameter for Matrix (" + struct_name + "." + memField.name + ")"); break; } + case MemoryFieldType::OnHeapPointer: + { + if (field.contains("pointertype")) + { + memField.jsonName = field["pointertype"].get(); + } + break; + } case MemoryFieldType::DefaultStructType: memField.jsonName = fieldTypeStr; break; @@ -908,6 +920,7 @@ uint8_t S2Plugin::Configuration::getAlingment(MemoryFieldType type) const case MemoryFieldType::StdList: case MemoryFieldType::EntityList: case MemoryFieldType::StdUnorderedMap: + case MemoryFieldType::OnHeapPointer: default: return sizeof(uintptr_t); } diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index b33d63d..e29d236 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -404,6 +404,26 @@ QStandardItem* S2Plugin::TreeViewMemoryFields::addMemoryField(const MemoryField& } break; } + case MemoryFieldType::OnHeapPointer: + { + returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); + if (field.jsonName.empty()) + break; + + // no isPointer check, for now + auto addr = Spelunky2::get()->get_HeapBase(); + addr += Script::Memory::ReadQword(memoryAddress); + if (auto fields = config->typeFieldsOfDefaultStruct(field.jsonName); !fields.empty()) + { + addMemoryFields(fields, fieldNameOverride, addr, 0, deltaPrefixCount + 1, returnField); + } + else + { + auto newField = config->nameToMemoryField(field.jsonName); + addMemoryField(newField, fieldNameOverride, addr, 0, deltaPrefixCount + 1, returnField); + } + break; + } case MemoryFieldType::DefaultStructType: { returnField = createAndInsertItem(field, fieldNameOverride, parent, memoryAddress); @@ -852,6 +872,30 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::OnHeapPointer: + { + std::optional value; + value = updateField(itemField, valueMemoryOffset, itemValue, "0x%016llX", itemValueHex, isPointer, "0x%016llX", true, !pointerUpdate, + highlightColor); + + if (comparisonActive) + { + std::optional comparisonValue; + comparisonValue = updateField(itemField, valueComparisonMemoryOffset, itemComparisonValue, "0x%016llX", itemComparisonValueHex, isPointer, "0x%016llX", false, false, + highlightColor); + itemComparisonValue->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + if (isPointer == false) + itemComparisonValueHex->setBackground(value != comparisonValue ? comparisonDifferenceColor : Qt::transparent); + } + if (shouldUpdateChildren) + { + for (uint8_t x = 0; x < itemField->rowCount(); ++x) + { + updateRow(x, std::nullopt, std::nullopt, itemField, disableChangeHighlighting); + } + } + break; + } case MemoryFieldType::Float: { std::optional value; @@ -1565,6 +1609,24 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional } break; } + case MemoryFieldType::LiquidPhysicsPointer: + { + if (valueMemoryOffset == 0) // nullptr or bad ptr + itemValue->setData(itemValueHex->data(Qt::DisplayRole), Qt::DisplayRole); + else + itemValue->setData("Show liquid physics", Qt::DisplayRole); + + if (comparisonActive) + { + if (valueComparisonMemoryOffset == 0) + itemComparisonValue->setData(itemComparisonValueHex->data(Qt::DisplayRole)); + else + itemComparisonValue->setData("Show liquid physics", Qt::DisplayRole); + + itemComparisonValue->setBackground(itemComparisonValueHex->background()); + } + break; + } case MemoryFieldType::ParticleDBPointer: { if (valueMemoryOffset == 0) // nullptr or bad ptr @@ -2310,6 +2372,16 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } break; } + case MemoryFieldType::OnHeapPointer: + { + auto offset = clickedItem->data(gsRoleRawValue).toULongLong(); + if (offset != 0) + { + GuiDumpAt(Spelunky2::get()->get_HeapBase() + offset); + GuiShowCpu(); + } + break; + } case MemoryFieldType::EntityPointer: { auto rawValue = clickedItem->data(gsRoleRawValue).toULongLong(); @@ -2429,6 +2501,15 @@ void S2Plugin::TreeViewMemoryFields::cellClicked(const QModelIndex& index) } break; } + case MemoryFieldType::LiquidPhysicsPointer: + { + auto rawValue = clickedItem->data(gsRoleMemoryAddress).toULongLong(); + if (rawValue != 0) + { + getToolbar()->showLiquidPhysics(rawValue); + } + break; + } case MemoryFieldType::StdVector: { auto addr = clickedItem->data(gsRoleMemoryAddress).toULongLong(); diff --git a/src/Views/ViewEntityDB.cpp b/src/Views/ViewEntityDB.cpp index cb2c7e0..e21765f 100644 --- a/src/Views/ViewEntityDB.cpp +++ b/src/Views/ViewEntityDB.cpp @@ -38,7 +38,7 @@ void S2Plugin::ViewEntityDB::showRAW(uintptr_t address) void S2Plugin::ViewEntityDB::showID(ID_type id) { - if (id > Configuration::get()->entityList().highestID()) + if (id > Configuration::get()->entityList().highestID() + 1) return; switchToLookupTab(); @@ -59,7 +59,7 @@ void S2Plugin::ViewEntityDB::label() const S2Plugin::ID_type S2Plugin::ViewEntityDB::highestRecordID() const { - return Configuration::get()->entityList().highestID(); + return Configuration::get()->entityList().highestID() + 1; // allow one more, since there is unused type } bool S2Plugin::ViewEntityDB::isValidRecordID(ID_type id) const diff --git a/src/Views/ViewToolbar.cpp b/src/Views/ViewToolbar.cpp index cebf24e..2c12d3a 100644 --- a/src/Views/ViewToolbar.cpp +++ b/src/Views/ViewToolbar.cpp @@ -6,6 +6,7 @@ #include "Views/ViewEntities.h" #include "Views/ViewEntity.h" #include "Views/ViewEntityDB.h" +#include "Views/ViewEntityFactory.h" #include "Views/ViewEntityList.h" #include "Views/ViewJournalPage.h" #include "Views/ViewLevelGen.h" @@ -67,6 +68,9 @@ S2Plugin::ViewToolbar::ViewToolbar(QMdiArea* mdiArea, QWidget* parent) : QDockWi addDivider(); mainLayout->addWidget(new QLabel("Game structs:"), 0, Qt::AlignHCenter); + auto btnEntityFactory = new QPushButton("Entity Factory", this); + mainLayout->addWidget(btnEntityFactory); + QObject::connect(btnEntityFactory, &QPushButton::clicked, this, &ViewToolbar::showEntityFactory); auto btnGameManager = new QPushButton("GameManager", this); mainLayout->addWidget(btnGameManager); QObject::connect(btnGameManager, &QPushButton::clicked, this, &ViewToolbar::showGameManager); @@ -99,6 +103,9 @@ S2Plugin::ViewToolbar::ViewToolbar(QMdiArea* mdiArea, QWidget* parent) : QDockWi auto btnLevelGen = new QPushButton("LevelGen", this); mainLayout->addWidget(btnLevelGen); QObject::connect(btnLevelGen, &QPushButton::clicked, this, &ViewToolbar::showMainThreadLevelGen); + auto btnLiquid = new QPushButton("Liquid Physics", this); + mainLayout->addWidget(btnLiquid); + QObject::connect(btnLiquid, &QPushButton::clicked, this, &ViewToolbar::showMainThreadLiquidPhysics); auto btnSaveGame = new QPushButton("SaveGame", this); mainLayout->addWidget(btnSaveGame); QObject::connect(btnSaveGame, &QPushButton::clicked, this, &ViewToolbar::showSaveGame); @@ -177,6 +184,14 @@ void S2Plugin::ViewToolbar::showLevelGen(uintptr_t address) win->setAttribute(Qt::WA_DeleteOnClose); } +void S2Plugin::ViewToolbar::showLiquidPhysics(uintptr_t address) +{ + auto w = new ViewStruct(address, Configuration::get()->typeFields(MemoryFieldType::LiquidPhysics), "LiquidPhysics"); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); +} + void S2Plugin::ViewToolbar::showEntity(uintptr_t address) { auto w = new ViewEntity(address); @@ -293,6 +308,16 @@ void S2Plugin::ViewToolbar::showMainThreadLevelGen() } } +void S2Plugin::ViewToolbar::showMainThreadLiquidPhysics() +{ + if (Spelunky2::is_loaded() && Configuration::is_loaded()) + { + auto ptr = Spelunky2::get()->get_LiquidEnginePtr(); + if (ptr != 0) + showLiquidPhysics(ptr); + } +} + void S2Plugin::ViewToolbar::showGameManager() { if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_GameManagerPtr() != 0) @@ -383,6 +408,17 @@ void S2Plugin::ViewToolbar::showHud() } } +void S2Plugin::ViewToolbar::showEntityFactory() +{ + if (Spelunky2::is_loaded() && Configuration::is_loaded() && Spelunky2::get()->get_EntityDB().isValid()) + { + auto w = new ViewEntityFactory(); + auto win = mMDIArea->addSubWindow(w); + win->setVisible(true); + win->setAttribute(Qt::WA_DeleteOnClose); + } +} + void S2Plugin::ViewToolbar::showLogger() { auto w = new ViewLogger(); From fbe48cec653c7014e3740bef4794274acf0d3f93 Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:28:49 +0200 Subject: [PATCH 18/20] little bit more unknown stuff --- resources/Spelunky2.json | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/resources/Spelunky2.json b/resources/Spelunky2.json index a771812..0388941 100644 --- a/resources/Spelunky2.json +++ b/resources/Spelunky2.json @@ -4093,9 +4093,7 @@ "type": "Float", "comment": "0 = still; 1 = follow immediately" }, - { "field": "unknown5", "type": "UnsignedDword" }, - { "field": "unknown6", "type": "UnsignedDword" }, - { "field": "unknown7", "type": "UnsignedDword" } + { "field": "unknown5", "type": "UnsignedDword" } ], "CameraBounds": [ { "field": "left", "type": "Float" }, @@ -8632,8 +8630,8 @@ { "field": "initial_slots", "type": "UnsignedDword" }, { "field": "slots_growth", "type": "UnsignedDword" }, { "field": "current_slots", "type": "UnsignedDword" }, - { "field": "unknwon1", "type": "UnsignedQword" }, - { "field": "unknwon2", "type": "StdVector", "valuetype": "EntityPointer", "pointer": true }, + { "field": "unknown1", "type": "UnsignedQword" }, + { "field": "pools_begin", "type": "StdVector", "valuetype": "DataPointer", "pointer": true, "comment": "saved the first entity address that causes the slot size to increase (including the initial)" }, { "field": "empty_buckets", "type": "StdVector", "valuetype": "DataPointer", "pointer": true } ], "EntityPoolOnHeapPointer": [ @@ -8645,8 +8643,8 @@ { "field": "skip1", "type": "Skip", "offset": 3 }, { "field": "entity_buckets", "type": "StdUnorderedMap", "keytype": "UnsignedDword", "valuetype": "EntityPoolOnHeapPointer", "comment": "key is the bucket size" }, { "field": "entity_names_map", "type": "StdUnorderedMap", "keytype": "StdString", "valuetype": "UnsignedWord" }, - { "field": "unknwon2", "type": "UnsignedQword" }, - { "field": "unknwon3", "type": "UnsignedQword" } + { "field": "unknown2", "type": "UnsignedQword" }, + { "field": "unknown3", "type": "UnsignedQword" } ] } } From d4245c624daef2108ce0f3528c9b63fae66d71b8 Mon Sep 17 00:00:00 2001 From: Clang-Format Bot Date: Fri, 28 Jun 2024 19:41:12 +0000 Subject: [PATCH 19/20] Automated clang-format changes --- src/QtHelpers/TreeViewMemoryFields.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/QtHelpers/TreeViewMemoryFields.cpp b/src/QtHelpers/TreeViewMemoryFields.cpp index e29d236..0599d06 100644 --- a/src/QtHelpers/TreeViewMemoryFields.cpp +++ b/src/QtHelpers/TreeViewMemoryFields.cpp @@ -1622,7 +1622,7 @@ void S2Plugin::TreeViewMemoryFields::updateRow(int row, std::optional itemComparisonValue->setData(itemComparisonValueHex->data(Qt::DisplayRole)); else itemComparisonValue->setData("Show liquid physics", Qt::DisplayRole); - + itemComparisonValue->setBackground(itemComparisonValueHex->background()); } break; From 0b660a4e1938b37e4c5e858d8b8b980243c5b81e Mon Sep 17 00:00:00 2001 From: Mr-Auto <36127424+Mr-Auto@users.noreply.github.com> Date: Fri, 28 Jun 2024 21:46:50 +0200 Subject: [PATCH 20/20] fix formatting in the CMakeList --- CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 331a578..95e5b65 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,7 +32,7 @@ find_package(Qt5Widgets REQUIRED) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) if(QT_NAMESPACE) - add_definitions(-DQT_NAMESPACE=${QT_NAMESPACE}) + add_definitions(-DQT_NAMESPACE=${QT_NAMESPACE}) endif() @@ -62,7 +62,7 @@ x64dbg_plugin(${PROJECT_NAME} include/Data/StdMap.h include/Data/EntityList.h include/Data/StdList.h - include/Data/StdUnorderedMap.h + include/Data/StdUnorderedMap.h include/Views/ViewToolbar.h include/Views/ViewEntityDB.h include/Views/ViewParticleDB.h @@ -80,10 +80,10 @@ x64dbg_plugin(${PROJECT_NAME} include/Views/ViewJournalPage.h include/Views/ViewThreads.h include/Views/ViewStdMap.h - include/Views/ViewStdUnorderedMap.h + include/Views/ViewStdUnorderedMap.h include/Views/ViewStdList.h - include/Views/ViewEntityList.h - include/Views/ViewEntityFactory.h + include/Views/ViewEntityList.h + include/Views/ViewEntityFactory.h include/QtHelpers/StyledItemDelegateHTML.h include/QtHelpers/StyledItemDelegateColorPicker.h include/QtHelpers/TreeViewMemoryFields.h @@ -138,7 +138,7 @@ x64dbg_plugin(${PROJECT_NAME} src/Views/ViewVirtualFunctions.cpp src/Views/ViewStdVector.cpp src/Views/ViewStdMap.cpp - src/Views/ViewStdUnorderedMap.cpp + src/Views/ViewStdUnorderedMap.cpp src/Views/ViewStdList.cpp src/Views/ViewJournalPage.cpp src/Views/ViewThreads.cpp