From e592650afcc17c765afac66123fe3206e3eb3183 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 6 Feb 2024 11:07:20 +0100 Subject: [PATCH 01/86] gdrom: set correct lead-out FAD of single-density area Use end FAD +1 of track 2 as start of lead-out for single density area. gdi: set track end FAD based on track size, not start of next track chd: ignore pad frames when calculating end FAD cdi: end FAD was one off Issue #1386 --- core/imgread/cdi.cpp | 2 +- core/imgread/chd.cpp | 2 +- core/imgread/common.cpp | 17 ++++++++++++++--- core/imgread/common.h | 15 +++------------ core/imgread/gdi.cpp | 8 +++++--- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/core/imgread/cdi.cpp b/core/imgread/cdi.cpp index 3f1dc1d46..4a3759cc7 100644 --- a/core/imgread/cdi.cpp +++ b/core/imgread/cdi.cpp @@ -135,7 +135,7 @@ Disc* cdi_parse(const char* file, std::vector *digest) else { std::fseek(fsource, track.total_length * track.sector_size, SEEK_CUR); - rv->EndFAD=track.start_lba +track.total_length; + rv->EndFAD = track.start_lba + track.total_length - 1; } track.position = std::ftell(fsource); diff --git a/core/imgread/chd.cpp b/core/imgread/chd.cpp index 757a673aa..0bb7417ff 100644 --- a/core/imgread/chd.cpp +++ b/core/imgread/chd.cpp @@ -189,7 +189,7 @@ void CHDDisc::tryOpen(const char* file) Track t; t.StartFAD = total_frames; total_frames += frames; - t.EndFAD = total_frames - 1; + t.EndFAD = total_frames - 1 - padframes; t.CTRL = strcmp(type,"AUDIO") == 0 ? 0 : 4; u32 sectorSize = getSectorSize(type); diff --git a/core/imgread/common.cpp b/core/imgread/common.cpp index 1566fae7a..151ec55dd 100644 --- a/core/imgread/common.cpp +++ b/core/imgread/common.cpp @@ -214,8 +214,7 @@ void libGDR_GetToc(u32* to, DiskArea area) to[100] = createTrackInfoFirstLast(disc->tracks[last_track - 1], last_track); if (disc->type == GdRom && area == SingleDensity) - // use smaller LEADOUT - to[101] = createTrackInfo(disc->LeadOut, 13085); + to[101] = createTrackInfo(disc->LeadOut, disc->tracks[1].EndFAD + 1); else to[101] = createTrackInfo(disc->LeadOut, disc->LeadOut.StartFAD); @@ -241,6 +240,18 @@ DiscType GuessDiscType(bool m1, bool m2, bool da) return CdRom; } +bool Disc::readSector(u32 FAD, u8 *dst, SectorFormat *sector_type, u8 *subcode, SubcodeFormat *subcode_type) +{ + for (size_t i = tracks.size(); i-- > 0; ) + { + *subcode_type = SUBFMT_NONE; + if (tracks[i].Read(FAD, dst, sector_type, subcode, subcode_type)) + return true; + } + + return false; +} + void Disc::ReadSectors(u32 FAD, u32 count, u8* dst, u32 fmt, LoadProgress *progress) { u8 temp[2448]; @@ -256,7 +267,7 @@ void Disc::ReadSectors(u32 FAD, u32 count, u8* dst, u32 fmt, LoadProgress *progr progress->label = "Loading..."; progress->progress = (float)i / count; } - if (ReadSector(FAD,temp,&secfmt,q_subchannel,&subfmt)) + if (readSector(FAD, temp, &secfmt, q_subchannel, &subfmt)) { //TODO: Proper sector conversions if (secfmt==SECFMT_2352) diff --git a/core/imgread/common.h b/core/imgread/common.h index 8da24bf51..78cafc3dd 100644 --- a/core/imgread/common.h +++ b/core/imgread/common.h @@ -112,18 +112,6 @@ struct Disc DiscType type; std::string catalog; - bool ReadSector(u32 FAD,u8* dst,SectorFormat* sector_type,u8* subcode,SubcodeFormat* subcode_type) - { - for (size_t i=tracks.size();i-->0;) - { - *subcode_type=SUBFMT_NONE; - if (tracks[i].Read(FAD,dst,sector_type,subcode,subcode_type)) - return true; - } - - return false; - } - void ReadSectors(u32 FAD, u32 count, u8 *dst, u32 fmt, LoadProgress *progress = nullptr); virtual ~Disc() @@ -211,6 +199,9 @@ struct Disc GetSessionInfo(ses, ses[2]); return (ses[3] << 16) | (ses[4] << 8) | (ses[5] << 0); } + +private: + bool readSector(u32 FAD, u8 *dst, SectorFormat *sector_type, u8 *subcode, SubcodeFormat *subcode_type); }; Disc* OpenDisc(const std::string& path, std::vector *digest = nullptr); diff --git a/core/imgread/gdi.cpp b/core/imgread/gdi.cpp index 2adf4af57..1197d3375 100644 --- a/core/imgread/gdi.cpp +++ b/core/imgread/gdi.cpp @@ -84,7 +84,7 @@ Disc* load_gdi(const char* file, std::vector *digest) t.StartFAD = FADS + 150; t.CTRL = CTRL; - if (SSIZE!=0) + if (SSIZE != 0) { std::string path = hostfs::storage().getSubPath(basepath, track_filename); FILE *file = hostfs::storage().openFile(path, "rb"); @@ -96,9 +96,11 @@ Disc* load_gdi(const char* file, std::vector *digest) if (digest != nullptr) md5.add(file); t.file = new RawTrackFile(file, OFFSET, t.StartFAD, SSIZE); + hostfs::FileInfo fileInfo = hostfs::storage().getFileInfo(path); + if ((fileInfo.size - OFFSET) % SSIZE != 0) + WARN_LOG(GDROM, "Warning: Size of track %s is not multiple of sector size %d", track_filename.c_str(), SSIZE); + t.EndFAD = t.StartFAD + (u32)(fileInfo.size - OFFSET) / SSIZE - 1; } - if (!disc->tracks.empty()) - disc->tracks.back().EndFAD = t.StartFAD - 1; disc->tracks.push_back(t); } From 9aa9b5bebc1d4f39555b2809a4a80cc94857039c Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 7 Feb 2024 11:05:09 +0100 Subject: [PATCH 02/86] ui: replace Exit by Close Game. Better format for some UI values. Issue #1383 --- core/rend/gui.cpp | 8 ++++---- core/rend/gui_util.cpp | 12 ++++++++++-- core/rend/gui_util.h | 2 +- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index c2be652dc..ab1a7b19a 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -650,7 +650,7 @@ static void gui_display_commands() ImGui::Columns(1, nullptr, false); // Exit - if (ImGui::Button("Exit", ScaledVec2(300, 50) + if (ImGui::Button(commandLineStart ? "Exit" : "Close Game", ScaledVec2(300, 50) + ImVec2(ImGui::GetStyle().ColumnsMinSpacing + ImGui::GetStyle().FramePadding.x * 2 - 1, 0))) { gui_stop_game(); @@ -2164,7 +2164,7 @@ static void gui_display_settings() ShowHelpMarker("Internal render resolution. Higher is better, but more demanding on the GPU. Values higher than your display resolution (but no more than double your display resolution) can be used for supersampling, which provides high-quality antialiasing without reducing sharpness."); OptionSlider("Horizontal Stretching", config::ScreenStretching, 100, 250, - "Stretch the screen horizontally"); + "Stretch the screen horizontally", "%d%%"); OptionArrowButtons("Frame Skipping", config::SkipFrame, 0, 6, "Number of frames to skip between two actually rendered frames"); } @@ -2230,7 +2230,7 @@ static void gui_display_settings() { #ifdef _OPENMP OptionArrowButtons("Texture Upscaling", config::TextureUpscale, 1, 8, - "Upscale textures with the xBRZ algorithm. Only on fast platforms and for certain 2D games"); + "Upscale textures with the xBRZ algorithm. Only on fast platforms and for certain 2D games", "x%d"); OptionSlider("Texture Max Size", config::MaxFilteredTextureSize, 8, 1024, "Textures larger than this dimension squared will not be upscaled"); OptionArrowButtons("Max Threads", config::MaxThreads, 1, 8, @@ -2293,7 +2293,7 @@ static void gui_display_settings() "Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms"); OptionCheckbox("Enable VMU Sounds", config::VmuSound, "Play VMU beeps when enabled."); - if (OptionSlider("Volume Level", config::AudioVolume, 0, 100, "Adjust the emulator's audio level")) + if (OptionSlider("Volume Level", config::AudioVolume, 0, 100, "Adjust the emulator's audio level", "%d%%")) { config::AudioVolume.calcDbPower(); }; diff --git a/core/rend/gui_util.cpp b/core/rend/gui_util.cpp index 3b7b44638..c836f7b9e 100644 --- a/core/rend/gui_util.cpp +++ b/core/rend/gui_util.cpp @@ -517,7 +517,7 @@ bool OptionSlider(const char *name, config::Option& option, template bool OptionSlider(const char *name, config::Option& option, int min, int max, const char *help, const char *format); template bool OptionSlider(const char *name, config::Option& option, int min, int max, const char *help, const char *format); -bool OptionArrowButtons(const char *name, config::Option& option, int min, int max, const char *help) +bool OptionArrowButtons(const char *name, config::Option& option, int min, int max, const char *help, const char *format) { const float innerSpacing = ImGui::GetStyle().ItemInnerSpacing.x; ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left @@ -526,7 +526,15 @@ bool OptionArrowButtons(const char *name, config::Option& option, int min, std::string id = "##" + std::string(name); ImGui::PushStyleVar(ImGuiStyleVar_DisabledAlpha, 1.0f); ImGui::BeginDisabled(); - ImGui::ButtonEx((std::to_string((int)option) + id).c_str(), ImVec2(width, 0)); + int size = snprintf(nullptr, 0, format, (int)option); + std::string value; + if (size >= 0) + { + value.resize(size + 1); + snprintf(value.data(), size + 1, format, (int)option); + value.resize(size); + } + ImGui::ButtonEx((value + id).c_str(), ImVec2(width, 0)); ImGui::EndDisabled(); ImGui::PopStyleVar(); ImGui::PopStyleColor(); diff --git a/core/rend/gui_util.h b/core/rend/gui_util.h index d6098f48f..931c07bd9 100644 --- a/core/rend/gui_util.h +++ b/core/rend/gui_util.h @@ -50,7 +50,7 @@ template bool OptionRadioButton(const char *name, config::Option& option, T value, const char *help = nullptr); void OptionComboBox(const char *name, config::Option& option, const char *values[], int count, const char *help = nullptr); -bool OptionArrowButtons(const char *name, config::Option& option, int min, int max, const char *help = nullptr); +bool OptionArrowButtons(const char *name, config::Option& option, int min, int max, const char *help = nullptr, const char *format = "%d"); static inline void centerNextWindow() { From 531c6f94d59abcb7f3a1d31d1dad32bbd4a4a820 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 7 Feb 2024 16:02:55 +0100 Subject: [PATCH 03/86] pvr: apply a negative bias to background plane depth Fixes skybox in Xtreme Sports. Issue #1381 Fixes background clipping in Blue Stinger (JP) intro. Issue #721 Fixes Windows CE yuv FMV black screen (4x4 EVO, Armada, Carsear's Palace, Giant Killers, PBA bowling, Starlancer, Tomr Raider, Wild Metal, Who wants to beat up...) Get rid of ForceWindowsCE option Force PAL for Super Runabout (EU) --- core/cfg/option.cpp | 1 - core/cfg/option.h | 1 - core/emulator.cpp | 17 +++-------------- core/hw/pvr/ta_vtx.cpp | 5 +++-- core/lua/lua.cpp | 2 -- core/rend/gui.cpp | 2 -- shell/libretro/libretro_core_options.h | 14 -------------- shell/libretro/option.cpp | 1 - tests/src/MmuTest.cpp | 1 - 9 files changed, 6 insertions(+), 38 deletions(-) diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index d39ccbfb6..8ef86252a 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -33,7 +33,6 @@ Option Cable("Dreamcast.Cable", 3); // TV Composite Option Region("Dreamcast.Region", 1); // USA Option Broadcast("Dreamcast.Broadcast", 0); // NTSC Option Language("Dreamcast.Language", 1); // English -Option ForceWindowsCE("Dreamcast.ForceWindowsCE"); Option AutoLoadState("Dreamcast.AutoLoadState"); Option AutoSaveState("Dreamcast.AutoSaveState"); Option SavestateSlot("Dreamcast.SavestateSlot"); diff --git a/core/cfg/option.h b/core/cfg/option.h index 65d41f394..78a2ff677 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -366,7 +366,6 @@ extern Option Cable; // 0 -> VGA, 1 -> VGA, 2 -> RGB, 3 -> TV Composite extern Option Region; // 0 -> JP, 1 -> USA, 2 -> EU, 3 -> default extern Option Broadcast; // 0 -> NTSC, 1 -> PAL, 2 -> PAL/M, 3 -> PAL/N, 4 -> default extern Option Language; // 0 -> JP, 1 -> EN, 2 -> DE, 3 -> FR, 4 -> SP, 5 -> IT, 6 -> default -extern Option ForceWindowsCE; extern Option AutoLoadState; extern Option AutoSaveState; extern Option SavestateSlot; diff --git a/core/emulator.cpp b/core/emulator.cpp index fe1d3a2de..53c617f14 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -47,7 +47,6 @@ #include settings_t settings; -constexpr float WINCE_DEPTH_SCALE = 0.01f; static void loadSpecialSettings() { @@ -56,13 +55,6 @@ static void loadSpecialSettings() if (settings.platform.isConsole()) { - if (ip_meta.isWindowsCE() || prod_id == "T26702N") // PBA Tour Bowling 2001 - { - INFO_LOG(BOOT, "Enabling Extra depth scaling for Windows CE game"); - config::ExtraDepthScale.override(WINCE_DEPTH_SCALE); - config::ForceWindowsCE.override(true); - } - // Tony Hawk's Pro Skater 2 if (prod_id == "T13008D 05" || prod_id == "T13006N" // Tony Hawk's Pro Skater 1 @@ -172,9 +164,7 @@ static void loadSpecialSettings() config::ExtraDepthScale.override(1e26f); } // Test Drive V-Rally - else if (prod_id == "T15110N" || prod_id == "T15105D 50" - // Caesars Palace 2000 - || prod_id == "T-12504N" || prod_id == "12502D-50") + else if (prod_id == "T15110N" || prod_id == "T15105D 50") { INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); config::ExtraDepthScale.override(0.1f); @@ -283,7 +273,8 @@ static void loadSpecialSettings() config::Broadcast.override(0); } else if (prod_id == "T-9709D-50" // San Francisco Rush 2049 (EU) - || prod_id == "T-8112D-50") // South Park Rally (EU) + || prod_id == "T-8112D-50" // South Park Rally (EU) + || prod_id == "T7014D 50") // Super Runabout (EU) { NOTICE_LOG(BOOT, "Forcing PAL broadcasting"); config::Broadcast.override(1); @@ -726,8 +717,6 @@ void loadGameSpecificSettings() // Reload per-game settings config::Settings::instance().load(true); - if (config::ForceWindowsCE && !config::ExtraDepthScale.isReadOnly()) - config::ExtraDepthScale.override(WINCE_DEPTH_SCALE); if (config::GGPOEnable) config::Sh4Clock.override(200); } diff --git a/core/hw/pvr/ta_vtx.cpp b/core/hw/pvr/ta_vtx.cpp index fcf80f967..b7cf54ad6 100644 --- a/core/hw/pvr/ta_vtx.cpp +++ b/core/hw/pvr/ta_vtx.cpp @@ -1605,8 +1605,9 @@ void FillBGP(TA_context* ctx) vertex_ptr += strip_vs; } - f32 bg_depth = ISP_BACKGND_D.f; - reinterpret_cast(bg_depth) &= 0xFFFFFFF0; // ISP_BACKGND_D has only 28 bits + // Apply a negative 1e-6 bias since the background plane is clipping too much + // (Fixes Xtreme Sports, Blue Stinger (JP) and many WinCE games using yuv FMV) + float bg_depth = std::max(ISP_BACKGND_D.f - 1e-6f, 1e-11f); cv[0].z = bg_depth; cv[1].z = bg_depth; cv[2].z = bg_depth; diff --git a/core/lua/lua.cpp b/core/lua/lua.cpp index 21b496881..da69edb79 100644 --- a/core/lua/lua.cpp +++ b/core/lua/lua.cpp @@ -129,7 +129,6 @@ CONFIG_ACCESSORS(Cable); CONFIG_ACCESSORS(Region); CONFIG_ACCESSORS(Broadcast); CONFIG_ACCESSORS(Language); -CONFIG_ACCESSORS(ForceWindowsCE); CONFIG_ACCESSORS(AutoLoadState); CONFIG_ACCESSORS(AutoSaveState); CONFIG_ACCESSORS(SavestateSlot); @@ -505,7 +504,6 @@ static void luaRegister(lua_State *L) CONFIG_PROPERTY(UseReios, bool) CONFIG_PROPERTY(FastGDRomLoad, bool) CONFIG_PROPERTY(OpenGlChecks, bool) - CONFIG_PROPERTY(ForceWindowsCE, bool) .endNamespace() .beginNamespace("network") diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index ab1a7b19a..fcbd55fee 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -2573,8 +2573,6 @@ static void gui_display_settings() header("Other"); { OptionCheckbox("HLE BIOS", config::UseReios, "Force high-level BIOS emulation"); - OptionCheckbox("Force Windows CE", config::ForceWindowsCE, - "Enable full MMU emulation and other Windows CE settings. Do not enable unless necessary"); OptionCheckbox("Multi-threaded emulation", config::ThreadedRendering, "Run the emulated CPU and GPU on different threads"); #ifndef __ANDROID diff --git a/shell/libretro/libretro_core_options.h b/shell/libretro/libretro_core_options.h index 1d1669f2c..a2d1c8f5c 100644 --- a/shell/libretro/libretro_core_options.h +++ b/shell/libretro/libretro_core_options.h @@ -189,20 +189,6 @@ struct retro_core_option_v2_definition option_defs_us[] = { "enabled", #endif }, - { - CORE_OPTION_NAME "_force_wince", - "Force Windows CE Mode", - NULL, - "Enable full MMU (Memory Management Unit) emulation and other settings for Windows CE games.", - NULL, - "system", - { - { "disabled", NULL }, - { "enabled", NULL }, - { NULL, NULL }, - }, - "disabled", - }, { CORE_OPTION_NAME "_allow_service_buttons", "Allow Arcade Service Buttons", diff --git a/shell/libretro/option.cpp b/shell/libretro/option.cpp index f7dac40c4..0489bd4d6 100644 --- a/shell/libretro/option.cpp +++ b/shell/libretro/option.cpp @@ -32,7 +32,6 @@ Option Cable("", 3); // TV Composite Option Region(CORE_OPTION_NAME "_region", 1); // USA Option Broadcast(CORE_OPTION_NAME "_broadcast", 0); // NTSC Option Language(CORE_OPTION_NAME "_language", 1); // English -Option ForceWindowsCE(CORE_OPTION_NAME "_force_wince"); Option AutoLoadState(""); Option AutoSaveState(""); Option SavestateSlot(""); diff --git a/tests/src/MmuTest.cpp b/tests/src/MmuTest.cpp index 899c01267..74933979e 100644 --- a/tests/src/MmuTest.cpp +++ b/tests/src/MmuTest.cpp @@ -31,7 +31,6 @@ protected: die("addrspace::reserve failed"); emu.init(); dc_reset(true); - config::ForceWindowsCE = true; CCN_MMUCR.AT = 1; MMU_reset(); } From d9d91381b5da5cc8263c7a8a774be8ce01dd69b4 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 7 Feb 2024 16:11:25 +0100 Subject: [PATCH 04/86] gdrom: delay "no disk" reporting to let the BIOS play the boot animation Thanks to kihato for the prototype code. Issue #587 --- core/imgread/common.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core/imgread/common.cpp b/core/imgread/common.cpp index 151ec55dd..6aa6853f5 100644 --- a/core/imgread/common.cpp +++ b/core/imgread/common.cpp @@ -125,7 +125,18 @@ static bool doDiscSwap(const std::string& path); bool InitDrive(const std::string& path) { bool rc = doDiscSwap(path); - gd_setdisc(); + if (rc && disc == nullptr) + { + // Drive is busy + sns_asc = 4; + sns_ascq = 1; + sns_key = 2; + SecNumber.Status = GD_BUSY; + sh4_sched_request(schedId, SH4_MAIN_CLOCK); + } + else { + gd_setdisc(); + } return rc; } From 434083c7160187d67b358f5ec4739937ba32f729 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 7 Feb 2024 19:41:03 +0100 Subject: [PATCH 05/86] samsptk: use cheat instead of extra depth scale Use a cheat to fix the game bug instead of dealing with ginormous depth values. --- core/cheats.cpp | 6 ++++++ core/emulator.cpp | 11 ----------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/core/cheats.cpp b/core/cheats.cpp index a14cd2641..77bdd50e6 100644 --- a/core/cheats.cpp +++ b/core/cheats.cpp @@ -505,6 +505,12 @@ void CheatManager::reset(const std::string& gameId) cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00135588, 1000); cheats.back().builtIn = true; } + else if (gameId == "SAMURAI SPIRITS 6" || gameId == "T0002M") + { + setActive(true); + cheats.emplace_back(Cheat::Type::setValue, "fix depth", true, 16, 0x0003e602, 0x0009); // nop (shift by 8 bits instead of 10) + cheats.back().builtIn = true; + } } if (config::WidescreenGameHacks) { diff --git a/core/emulator.cpp b/core/emulator.cpp index 53c617f14..983bc5200 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -157,12 +157,6 @@ static void loadSpecialSettings() INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); config::ExtraDepthScale.override(100.f); } - // Samurai Shodown 6 dc port - else if (prod_id == "T0002M") - { - INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); - config::ExtraDepthScale.override(1e26f); - } // Test Drive V-Rally else if (prod_id == "T15110N" || prod_id == "T15105D 50") { @@ -297,11 +291,6 @@ static void loadSpecialSettings() } else if (settings.platform.isArcade()) { - if (prod_id == "SAMURAI SPIRITS 6") - { - INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); - config::ExtraDepthScale.override(1e26f); - } if (prod_id == "COSMIC SMASH IN JAPAN") { INFO_LOG(BOOT, "Enabling translucent depth multipass for game %s", prod_id.c_str()); From 90b13a40ab6ecdc381f3e152459e63b0589a1528 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 10 Feb 2024 12:06:48 +0100 Subject: [PATCH 06/86] gladLoader: check return code and log. cheats: light refactoring --- core/cheats.cpp | 74 ++++++++++++++++-------------------------------- core/cheats.h | 20 ++++++------- core/sdl/sdl.cpp | 13 +++++---- core/wsi/egl.cpp | 7 ++++- core/wsi/sdl.cpp | 6 +++- 5 files changed, 51 insertions(+), 69 deletions(-) diff --git a/core/cheats.cpp b/core/cheats.cpp index 77bdd50e6..aa6b7b343 100644 --- a/core/cheats.cpp +++ b/core/cheats.cpp @@ -430,39 +430,26 @@ void CheatManager::reset(const std::string& gameId) if (!cheatFile.empty()) loadCheatFile(cheatFile); #endif + size_t cheatCount = cheats.size(); if (gameId == "Fixed BOOT strapper") // Extreme Hunting 2 { - setActive(true); - cheats.emplace_back(Cheat::Type::runNextIfEq, "skip netbd check ifeq", true, 32, 0x00067b04, 0); - cheats.back().builtIn = true; - cheats.emplace_back(Cheat::Type::setValue, "skip netbd check", true, 32, 0x00067b04, 1); // 1 skips initNetwork() - cheats.back().builtIn = true; - cheats.emplace_back(Cheat::Type::setValue, "skip netbd check 2", true, 16, 0x0009acc8, 0x0009); // not acceptable by main board - cheats.back().builtIn = true; + cheats.emplace_back(Cheat::Type::runNextIfEq, "skip netbd check ifeq", true, 32, 0x00067b04, 0, true); + cheats.emplace_back(Cheat::Type::setValue, "skip netbd check", true, 32, 0x00067b04, 1, true); // 1 skips initNetwork() + cheats.emplace_back(Cheat::Type::setValue, "skip netbd check 2", true, 16, 0x0009acc8, 0x0009, true); // not acceptable by main board // ac010000 should be d202 9302, but is changed to 78c0 8c93 - cheats.emplace_back(Cheat::Type::runNextIfEq, "fix boot ifeq", true, 32, 0x00010000, 0x8c9378c0); - cheats.back().builtIn = true; - cheats.emplace_back(Cheat::Type::setValue, "fix boot", true, 32, 0x00010000, 0x9302d202); - cheats.back().builtIn = true; + cheats.emplace_back(Cheat::Type::runNextIfEq, "fix boot ifeq", true, 32, 0x00010000, 0x8c9378c0, true); + cheats.emplace_back(Cheat::Type::setValue, "fix boot", true, 32, 0x00010000, 0x9302d202, true); } - else if (gameId == "THE KING OF ROUTE66") - { - setActive(true); - cheats.emplace_back(Cheat::Type::setValue, "ignore drive error", true, 32, 0x00023ee0, 0x0009000B); // rts, nop - cheats.back().builtIn = true; + else if (gameId == "THE KING OF ROUTE66") { + cheats.emplace_back(Cheat::Type::setValue, "ignore drive error", true, 32, 0x00023ee0, 0x0009000B, true); // rts, nop } else if (gameId.substr(0, 8) == "MKG TKOB") { const auto& setMushikingCheats = [this](u32 addr) { - setActive(true); - cheats.emplace_back(Cheat::Type::setValue, "ignore rfid1 error", true, 32, addr, 0); // rfid[0].error = 0 - cheats.back().builtIn = true; - cheats.emplace_back(Cheat::Type::setValue, "ignore rfid2 error", true, 32, addr + 0x48, 0); // rfid[1].error = 0 - cheats.back().builtIn = true; - cheats.emplace_back(Cheat::Type::setValue, "ignore rfid1 status", true, 32, addr + 8, 0); // rfid[0].data18 = 0 - cheats.back().builtIn = true; - cheats.emplace_back(Cheat::Type::setValue, "ignore rfid2 status", true, 32, addr + 0x50, 0); // rfid[1].data18 = 0 - cheats.back().builtIn = true; + cheats.emplace_back(Cheat::Type::setValue, "ignore rfid1 error", true, 32, addr, 0, true); // rfid[0].error = 0 + cheats.emplace_back(Cheat::Type::setValue, "ignore rfid2 error", true, 32, addr + 0x48, 0, true); // rfid[1].error = 0 + cheats.emplace_back(Cheat::Type::setValue, "ignore rfid1 status", true, 32, addr + 8, 0, true); // rfid[0].data18 = 0 + cheats.emplace_back(Cheat::Type::setValue, "ignore rfid2 status", true, 32, addr + 0x50, 0, true); // rfid[1].data18 = 0 }; if (gameId == "MKG TKOB 2 EXP VER1.001-") // mushi2eo setMushikingCheats(0x6fe1bc); @@ -481,36 +468,23 @@ void CheatManager::reset(const std::string& gameId) else if (gameId == "MKG TKOB 2 KOR VER1.000-") // mushik2k setMushikingCheats(0x706084); } - else if (gameId == "T-8120N") // Dave Mirra BMX (US) - { - setActive(true); - cheats.emplace_back(Cheat::Type::setValue, "fix main loop time", true, 32, 0x0030b8cc, 0x42040000); // 33.0 ms - cheats.back().builtIn = true; + else if (gameId == "T-8120N") { // Dave Mirra BMX (US) + cheats.emplace_back(Cheat::Type::setValue, "fix main loop time", true, 32, 0x0030b8cc, 0x42040000, true); // 33.0 ms } - else if (gameId == "T8120D 50") // Dave Mirra BMX (EU) - { - setActive(true); - cheats.emplace_back(Cheat::Type::setValue, "fix main loop time", true, 32, 0x003011cc, 0x42200000); // 40.0 ms - cheats.back().builtIn = true; + else if (gameId == "T8120D 50") { // Dave Mirra BMX (EU) + cheats.emplace_back(Cheat::Type::setValue, "fix main loop time", true, 32, 0x003011cc, 0x42200000, true); // 40.0 ms } - else if (gameId == "MK-0100") // F355 US - { - setActive(true); - cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00131668, 1000); - cheats.back().builtIn = true; + else if (gameId == "MK-0100") { // F355 US + cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00131668, 1000, true); } - else if (gameId == "T8118D 50") // F355 EU - { - setActive(true); - cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00135588, 1000); - cheats.back().builtIn = true; + else if (gameId == "T8118D 50") { // F355 EU + cheats.emplace_back(Cheat::Type::setValue, "increase datapump timeout", true, 16, 0x00135588, 1000, true); } - else if (gameId == "SAMURAI SPIRITS 6" || gameId == "T0002M") - { - setActive(true); - cheats.emplace_back(Cheat::Type::setValue, "fix depth", true, 16, 0x0003e602, 0x0009); // nop (shift by 8 bits instead of 10) - cheats.back().builtIn = true; + else if (gameId == "SAMURAI SPIRITS 6" || gameId == "T0002M") { + cheats.emplace_back(Cheat::Type::setValue, "fix depth", true, 16, 0x0003e602, 0x0009, true); // nop (shift by 8 bits instead of 10) } + if (cheats.size() > cheatCount) + setActive(true); } if (config::WidescreenGameHacks) { diff --git a/core/cheats.h b/core/cheats.h index cd68b8d41..709939d76 100644 --- a/core/cheats.h +++ b/core/cheats.h @@ -51,19 +51,17 @@ struct Cheat u32 size; u32 address; u32 value; - u8 valueMask; - u32 repeatCount; - u32 repeatValueIncrement; - u32 repeatAddressIncrement; - u32 destAddress; + u8 valueMask = 0; + u32 repeatCount = 1; + u32 repeatValueIncrement = 0; + u32 repeatAddressIncrement = 0; + u32 destAddress = 0; bool builtIn; - Cheat(Type type = Type::disabled, const std::string& description = "", bool enabled = false, u32 size = 0, u32 address = 0, - u32 value = 0, u8 valueMask = 0, u32 repeatCount = 1, u32 repeatValueIncrement = 0, - u32 repeatAddressIncrement = 0, u32 destAddress = 0, bool builtIn = false) - : type(type), description(description), enabled(enabled), size(size), address(address), value(value), valueMask(valueMask), - repeatCount(repeatCount), repeatValueIncrement(repeatValueIncrement), repeatAddressIncrement(repeatAddressIncrement), - destAddress(destAddress), builtIn(builtIn) + Cheat(Type type = Type::disabled, const std::string& description = "", bool enabled = false, + u32 size = 0, u32 address = 0, u32 value = 0, bool builtIn = false) + : type(type), description(description), enabled(enabled), + size(size), address(address), value(value), builtIn(builtIn) { } }; diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index 8fcaf7f2a..da9164107 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -843,14 +843,11 @@ static KeyboardLayout detectKeyboardLayout() { // PT or BR key = SDL_GetKeyFromScancode(SDL_SCANCODE_RIGHTBRACKET); - if (key == SDLK_LEFTBRACKET) { + if (key == SDLK_LEFTBRACKET) INFO_LOG(INPUT, "Portuguese (BR) keyboard detected"); - return KeyboardLayout::PT; - } - else { + else INFO_LOG(INPUT, "Portuguese keyboard detected"); - return KeyboardLayout::PT; - } + return KeyboardLayout::PT; } key = SDL_GetKeyFromScancode(SDL_SCANCODE_MINUS); if (key == SDLK_PLUS) { @@ -904,6 +901,10 @@ static KeyboardLayout detectKeyboardLayout() return KeyboardLayout::US; } +// All known card games use simple Code 39 barcodes. +// The barcode scanner should be configured to use HID-USB (act like a keyboard) +// and use '*' as preamble and terminator, which are the Code 39 start and stop characters. +// So disable the default terminator ('\n') and enable sending the Code 39 start and stop characters. static bool handleBarcodeScanner(const SDL_Event& event) { static const std::unordered_map keymapDefault { diff --git a/core/wsi/egl.cpp b/core/wsi/egl.cpp index 81a7042b9..d55993381 100644 --- a/core/wsi/egl.cpp +++ b/core/wsi/egl.cpp @@ -39,7 +39,12 @@ bool EGLGraphicsContext::makeCurrent() bool EGLGraphicsContext::init() { - gladLoaderLoadEGL(NULL); + int version = gladLoaderLoadEGL(EGL_NO_DISPLAY); + if (version == 0) { + ERROR_LOG(RENDERER, "Failed to load libEGL.so"); + return false; + } + NOTICE_LOG(RENDERER, "EGL version %d.%d", GLAD_VERSION_MAJOR(version), GLAD_VERSION_MINOR(version)); //try to get a display display = eglGetDisplay((EGLNativeDisplayType)display); diff --git a/core/wsi/sdl.cpp b/core/wsi/sdl.cpp index cbcb9390c..e187a8e6a 100644 --- a/core/wsi/sdl.cpp +++ b/core/wsi/sdl.cpp @@ -99,7 +99,11 @@ bool SDLGLGraphicsContext::init() SDL_GL_SetSwapInterval(swapOnVSync ? swapInterval : 0); #ifdef GLES - gladLoadGLES2((GLADloadfunc) SDL_GL_GetProcAddress); + if (gladLoadGLES2((GLADloadfunc)SDL_GL_GetProcAddress) == 0) + { + ERROR_LOG(RENDERER, "gladLoadGLES2 failed"); + return false; + } #else if (!gladLoadGL((GLADloadfunc) SDL_GL_GetProcAddress) || !GLAD_GL_VERSION_3_0) { From 232924d88a5f4182ce799c1bbe213ac5b525b9ca Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 10 Feb 2024 12:41:17 +0100 Subject: [PATCH 07/86] init drive when running .elf. Skip special settings for BIOS and .elf. --- core/emulator.cpp | 18 +++++++----------- core/stdclass.cpp | 2 ++ core/stdclass.h | 6 ++++-- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core/emulator.cpp b/core/emulator.cpp index ee058cf37..27beaadf9 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -151,8 +151,8 @@ static void loadSpecialSettings() INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); config::ExtraDepthScale.override(1000000.f); // Mali needs 1M, 10K is enough for others } - // Re-Volt (US, EU) - else if (prod_id == "T-8109N" || prod_id == "T8107D 50") + // Re-Volt (US, EU, JP) + else if (prod_id == "T-8109N" || prod_id == "T8107D 50" || prod_id == "T-8101M") { INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); config::ExtraDepthScale.override(100.f); @@ -169,12 +169,6 @@ static void loadSpecialSettings() INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); config::ExtraDepthScale.override(1000.f); } - // Re-Volt (JP) - else if (prod_id == "T-8101M") - { - INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); - config::ExtraDepthScale.override(100.f); - } std::string areas(ip_meta.area_symbols, sizeof(ip_meta.area_symbols)); bool region_usa = areas.find('U') != std::string::npos; @@ -311,9 +305,6 @@ static void loadSpecialSettings() config::ForceFreePlay.override(false); } } - // Graphics context isn't available yet in libretro - if (GraphicsContext::Instance() != nullptr && GraphicsContext::Instance()->isAMD()) - config::NativeDepthInterpolation.override(true); } void dc_reset(bool hard) @@ -510,6 +501,7 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) { // Elf only supported with HLE BIOS nvmem::loadHle(); + InitDrive(""); } } @@ -692,6 +684,10 @@ void Emulator::requestReset() void loadGameSpecificSettings() { + // Graphics context isn't available yet in libretro + if (GraphicsContext::Instance() != nullptr && GraphicsContext::Instance()->isAMD()) + config::NativeDepthInterpolation.override(true); + if (settings.platform.isConsole()) { reios_disk_id(); diff --git a/core/stdclass.cpp b/core/stdclass.cpp index c3f419482..b46215226 100644 --- a/core/stdclass.cpp +++ b/core/stdclass.cpp @@ -20,6 +20,8 @@ static std::string user_data_dir; static std::vector system_config_dirs; static std::vector system_data_dirs; +const std::string defaultWs(" \0", 2); + bool file_exists(const std::string& filename) { return (flycast::access(filename.c_str(), R_OK) == 0); diff --git a/core/stdclass.h b/core/stdclass.h index d2e83a78a..36fe655be 100644 --- a/core/stdclass.h +++ b/core/stdclass.h @@ -129,8 +129,10 @@ static inline std::string get_file_basename(const std::string& s) return s.substr(0, dot); } +extern const std::string defaultWs; + static inline std::string trim_trailing_ws(const std::string& str, - const std::string& whitespace = " ") + const std::string& whitespace = defaultWs) { const auto strEnd = str.find_last_not_of(whitespace); if (strEnd == std::string::npos) @@ -140,7 +142,7 @@ static inline std::string trim_trailing_ws(const std::string& str, } static inline std::string trim_ws(const std::string& str, - const std::string& whitespace = " ") + const std::string& whitespace = defaultWs) { const auto strStart = str.find_first_not_of(whitespace); if (strStart == std::string::npos) From 6cb929953c097135032d6be1c16ba4503fde5eca Mon Sep 17 00:00:00 2001 From: scribam Date: Sat, 10 Feb 2024 10:37:51 +0100 Subject: [PATCH 08/86] ci: use latest devkitpro docker image --- .github/workflows/switch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/switch.yml b/.github/workflows/switch.yml index 5039fe4bb..4d6398e44 100644 --- a/.github/workflows/switch.yml +++ b/.github/workflows/switch.yml @@ -6,7 +6,7 @@ jobs: build: name: ${{ matrix.config.name }} runs-on: ubuntu-latest - container: devkitpro/devkita64:20240120 + container: devkitpro/devkita64:latest strategy: matrix: From eb2826f6d37255b7c3e9bfe37db54362b2e429ea Mon Sep 17 00:00:00 2001 From: scribam Date: Thu, 15 Feb 2024 09:10:00 +0100 Subject: [PATCH 09/86] deps: update libzip to version 1.10.1 --- CMakeLists.txt | 3 +- core/deps/libzip/.clang-format | 12 + .../.github/ISSUE_TEMPLATE/bug-report.md | 32 + .../.github/ISSUE_TEMPLATE/compile-error.md | 25 + .../.github/ISSUE_TEMPLATE/feature-request.md | 22 + core/deps/libzip/.github/workflows/CIFuzz.yml | 25 + core/deps/libzip/.github/workflows/build.yml | 65 + .../.github/workflows/codeql-analysis.yml | 74 + core/deps/libzip/API-CHANGES.md | 12 + core/deps/libzip/CMakeLists.txt | 119 +- core/deps/libzip/INSTALL.md | 14 +- core/deps/libzip/LICENSE | 2 +- core/deps/libzip/NEWS.md | 189 +- core/deps/libzip/README.md | 17 +- core/deps/libzip/SECURITY.md | 13 + core/deps/libzip/THANKS | 45 + core/deps/libzip/TODO.md | 27 +- core/deps/libzip/android/do.sh | 2 +- core/deps/libzip/android/readme.txt | 3 + core/deps/libzip/appveyor.yml | 90 + core/deps/libzip/cmake-config.h.in | 12 +- core/deps/libzip/cmake/Dist.cmake | 8 +- core/deps/libzip/cmake/FindMbedTLS.cmake | 13 +- core/deps/libzip/cmake/FindNettle.cmake | 2 +- core/deps/libzip/cmake/FindZstd.cmake | 135 - core/deps/libzip/cmake/Findzstd.cmake | 186 ++ .../cmake/GenerateZipErrorStrings.cmake | 47 + core/deps/libzip/developer-xcode/Info.plist | 46 - .../developer-xcode/README Xcode Project.md | 6 - core/deps/libzip/developer-xcode/config.h | 94 - .../libzip/developer-xcode/extract-version.sh | 20 - .../libzip.xcodeproj/project.pbxproj | 2934 ----------------- .../contents.xcworkspacedata | 7 - .../xcshareddata/IDEWorkspaceChecks.plist | 8 - .../deps/libzip/developer-xcode/mkconfig-h.sh | 12 - .../deps/libzip/developer-xcode/zip_err_str.c | 84 - core/deps/libzip/developer-xcode/zipconf.h | 47 - core/deps/libzip/examples/CMakeLists.txt | 8 +- .../libzip/examples/add-compressed-data.c | 171 + core/deps/libzip/examples/autoclose-archive.c | 160 + core/deps/libzip/examples/in-memory.c | 4 +- core/deps/libzip/examples/windows-open.c | 4 +- core/deps/libzip/lib/CMakeLists.txt | 53 +- core/deps/libzip/lib/compat.h | 52 +- core/deps/libzip/lib/zip.h | 94 +- core/deps/libzip/lib/zip_add.c | 4 +- core/deps/libzip/lib/zip_add_dir.c | 4 +- core/deps/libzip/lib/zip_add_entry.c | 4 +- core/deps/libzip/lib/zip_algorithm_bzip2.c | 30 +- core/deps/libzip/lib/zip_algorithm_deflate.c | 39 +- core/deps/libzip/lib/zip_algorithm_xz.c | 39 +- core/deps/libzip/lib/zip_algorithm_zstd.c | 30 +- core/deps/libzip/lib/zip_buffer.c | 25 +- core/deps/libzip/lib/zip_close.c | 127 +- core/deps/libzip/lib/zip_crypto.h | 4 +- .../deps/libzip/lib/zip_crypto_commoncrypto.c | 4 +- .../deps/libzip/lib/zip_crypto_commoncrypto.h | 2 +- core/deps/libzip/lib/zip_crypto_gnutls.c | 9 +- core/deps/libzip/lib/zip_crypto_gnutls.h | 4 +- core/deps/libzip/lib/zip_crypto_mbedtls.c | 13 +- core/deps/libzip/lib/zip_crypto_mbedtls.h | 4 +- core/deps/libzip/lib/zip_crypto_openssl.c | 108 +- core/deps/libzip/lib/zip_crypto_openssl.h | 29 +- core/deps/libzip/lib/zip_crypto_win.c | 12 +- core/deps/libzip/lib/zip_crypto_win.h | 4 +- core/deps/libzip/lib/zip_delete.c | 4 +- core/deps/libzip/lib/zip_dir_add.c | 8 +- core/deps/libzip/lib/zip_dirent.c | 250 +- core/deps/libzip/lib/zip_discard.c | 4 +- core/deps/libzip/lib/zip_entry.c | 4 +- core/deps/libzip/lib/zip_err_str.c | 84 - core/deps/libzip/lib/zip_error.c | 30 +- core/deps/libzip/lib/zip_error_clear.c | 4 +- core/deps/libzip/lib/zip_error_get.c | 4 +- core/deps/libzip/lib/zip_error_get_sys_type.c | 9 +- core/deps/libzip/lib/zip_error_strerror.c | 97 +- core/deps/libzip/lib/zip_error_to_str.c | 30 +- core/deps/libzip/lib/zip_extra_field.c | 13 +- core/deps/libzip/lib/zip_extra_field_api.c | 16 +- core/deps/libzip/lib/zip_fclose.c | 4 +- core/deps/libzip/lib/zip_fdopen.c | 9 +- core/deps/libzip/lib/zip_file_add.c | 4 +- core/deps/libzip/lib/zip_file_error_clear.c | 4 +- core/deps/libzip/lib/zip_file_error_get.c | 4 +- core/deps/libzip/lib/zip_file_get_comment.c | 4 +- .../lib/zip_file_get_external_attributes.c | 4 +- core/deps/libzip/lib/zip_file_get_offset.c | 10 +- core/deps/libzip/lib/zip_file_rename.c | 4 +- core/deps/libzip/lib/zip_file_replace.c | 4 +- core/deps/libzip/lib/zip_file_set_comment.c | 8 +- .../deps/libzip/lib/zip_file_set_encryption.c | 8 +- .../lib/zip_file_set_external_attributes.c | 8 +- core/deps/libzip/lib/zip_file_set_mtime.c | 13 +- core/deps/libzip/lib/zip_file_strerror.c | 4 +- core/deps/libzip/lib/zip_fopen.c | 4 +- core/deps/libzip/lib/zip_fopen_encrypted.c | 4 +- core/deps/libzip/lib/zip_fopen_index.c | 4 +- .../libzip/lib/zip_fopen_index_encrypted.c | 10 +- core/deps/libzip/lib/zip_fread.c | 9 +- core/deps/libzip/lib/zip_fseek.c | 16 +- core/deps/libzip/lib/zip_ftell.c | 6 +- .../deps/libzip/lib/zip_get_archive_comment.c | 4 +- core/deps/libzip/lib/zip_get_archive_flag.c | 4 +- .../lib/zip_get_encryption_implementation.c | 4 +- core/deps/libzip/lib/zip_get_file_comment.c | 4 +- core/deps/libzip/lib/zip_get_name.c | 4 +- core/deps/libzip/lib/zip_get_num_entries.c | 4 +- core/deps/libzip/lib/zip_get_num_files.c | 4 +- core/deps/libzip/lib/zip_hash.c | 4 +- core/deps/libzip/lib/zip_io_util.c | 22 +- core/deps/libzip/lib/zip_libzip_version.c | 4 +- core/deps/libzip/lib/zip_memdup.c | 6 +- core/deps/libzip/lib/zip_name_locate.c | 34 +- core/deps/libzip/lib/zip_new.c | 4 +- core/deps/libzip/lib/zip_open.c | 224 +- core/deps/libzip/lib/zip_pkware.c | 2 +- core/deps/libzip/lib/zip_progress.c | 2 +- core/deps/libzip/lib/zip_random_unix.c | 10 +- core/deps/libzip/lib/zip_random_uwp.c | 10 +- core/deps/libzip/lib/zip_random_win32.c | 5 +- core/deps/libzip/lib/zip_rename.c | 4 +- core/deps/libzip/lib/zip_replace.c | 4 +- .../deps/libzip/lib/zip_set_archive_comment.c | 8 +- core/deps/libzip/lib/zip_set_archive_flag.c | 23 +- .../libzip/lib/zip_set_default_password.c | 4 +- core/deps/libzip/lib/zip_set_file_comment.c | 4 +- .../libzip/lib/zip_set_file_compression.c | 10 +- core/deps/libzip/lib/zip_set_name.c | 4 +- .../deps/libzip/lib/zip_source_accept_empty.c | 8 +- core/deps/libzip/lib/zip_source_begin_write.c | 9 +- .../lib/zip_source_begin_write_cloning.c | 9 +- core/deps/libzip/lib/zip_source_buffer.c | 69 +- core/deps/libzip/lib/zip_source_call.c | 4 +- core/deps/libzip/lib/zip_source_close.c | 4 +- .../deps/libzip/lib/zip_source_commit_write.c | 9 +- core/deps/libzip/lib/zip_source_compress.c | 26 +- core/deps/libzip/lib/zip_source_crc.c | 34 +- core/deps/libzip/lib/zip_source_error.c | 4 +- core/deps/libzip/lib/zip_source_file.h | 6 +- core/deps/libzip/lib/zip_source_file_common.c | 36 +- core/deps/libzip/lib/zip_source_file_stdio.c | 34 +- core/deps/libzip/lib/zip_source_file_stdio.h | 4 +- .../libzip/lib/zip_source_file_stdio_named.c | 223 +- core/deps/libzip/lib/zip_source_file_win32.c | 10 +- core/deps/libzip/lib/zip_source_file_win32.h | 10 +- .../libzip/lib/zip_source_file_win32_ansi.c | 8 +- .../libzip/lib/zip_source_file_win32_named.c | 3 +- .../libzip/lib/zip_source_file_win32_utf16.c | 8 +- .../libzip/lib/zip_source_file_win32_utf8.c | 4 +- core/deps/libzip/lib/zip_source_free.c | 4 +- core/deps/libzip/lib/zip_source_function.c | 5 +- .../lib/zip_source_get_file_attributes.c | 6 +- core/deps/libzip/lib/zip_source_is_deleted.c | 4 +- core/deps/libzip/lib/zip_source_layered.c | 24 +- core/deps/libzip/lib/zip_source_open.c | 6 +- ...mpm.c => zip_source_pass_to_lower_layer.c} | 91 +- .../libzip/lib/zip_source_pkware_decode.c | 11 +- .../libzip/lib/zip_source_pkware_encode.c | 42 +- core/deps/libzip/lib/zip_source_read.c | 4 +- core/deps/libzip/lib/zip_source_remove.c | 9 +- .../libzip/lib/zip_source_rollback_write.c | 8 +- core/deps/libzip/lib/zip_source_seek.c | 4 +- core/deps/libzip/lib/zip_source_seek_write.c | 9 +- core/deps/libzip/lib/zip_source_stat.c | 10 +- core/deps/libzip/lib/zip_source_supports.c | 13 +- core/deps/libzip/lib/zip_source_tell.c | 4 +- core/deps/libzip/lib/zip_source_tell_write.c | 9 +- core/deps/libzip/lib/zip_source_window.c | 109 +- .../libzip/lib/zip_source_winzip_aes_decode.c | 20 +- .../libzip/lib/zip_source_winzip_aes_encode.c | 13 +- core/deps/libzip/lib/zip_source_write.c | 4 +- core/deps/libzip/lib/zip_source_zip.c | 33 +- core/deps/libzip/lib/zip_source_zip_new.c | 249 +- core/deps/libzip/lib/zip_stat.c | 4 +- core/deps/libzip/lib/zip_stat_index.c | 39 +- core/deps/libzip/lib/zip_stat_init.c | 4 +- core/deps/libzip/lib/zip_strerror.c | 4 +- core/deps/libzip/lib/zip_string.c | 6 +- core/deps/libzip/lib/zip_unchange.c | 29 +- core/deps/libzip/lib/zip_unchange_all.c | 4 +- core/deps/libzip/lib/zip_unchange_archive.c | 4 +- core/deps/libzip/lib/zip_unchange_data.c | 4 +- core/deps/libzip/lib/zip_utf-8.c | 4 +- core/deps/libzip/lib/zip_winzip_aes.c | 6 +- core/deps/libzip/lib/zipint.h | 106 +- core/deps/libzip/libzip-config.cmake.in | 42 +- core/deps/libzip/libzip.pc.in | 7 +- core/deps/libzip/man/CMakeLists.txt | 21 +- core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.html | 2 +- core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.man | 2 +- core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.mdoc | 2 +- core/deps/libzip/man/fix-man-links.sh | 6 - core/deps/libzip/man/libzip.html | 97 +- core/deps/libzip/man/libzip.man | 85 +- core/deps/libzip/man/libzip.mdoc | 75 +- core/deps/libzip/man/links | 4 + core/deps/libzip/man/make_zip_errors.sh | 114 - core/deps/libzip/man/mkdocset.pl | 216 -- core/deps/libzip/man/nih-man.css | 61 - core/deps/libzip/man/update-html.cmake | 10 +- core/deps/libzip/man/update-man.cmake | 12 +- core/deps/libzip/man/zip_add.html | 2 +- core/deps/libzip/man/zip_add.man | 2 +- core/deps/libzip/man/zip_add.mdoc | 2 +- core/deps/libzip/man/zip_add_dir.html | 2 +- core/deps/libzip/man/zip_add_dir.man | 2 +- core/deps/libzip/man/zip_add_dir.mdoc | 2 +- core/deps/libzip/man/zip_close.html | 24 +- core/deps/libzip/man/zip_close.man | 27 +- core/deps/libzip/man/zip_close.mdoc | 27 +- .../man/zip_compression_method_supported.html | 2 +- .../man/zip_compression_method_supported.man | 2 +- .../man/zip_compression_method_supported.mdoc | 2 +- core/deps/libzip/man/zip_delete.html | 2 +- core/deps/libzip/man/zip_delete.man | 2 +- core/deps/libzip/man/zip_delete.mdoc | 2 +- core/deps/libzip/man/zip_dir_add.html | 2 +- core/deps/libzip/man/zip_dir_add.man | 2 +- core/deps/libzip/man/zip_dir_add.mdoc | 2 +- core/deps/libzip/man/zip_discard.html | 2 +- core/deps/libzip/man/zip_discard.man | 2 +- core/deps/libzip/man/zip_discard.mdoc | 2 +- .../man/zip_encryption_method_supported.html | 2 +- .../man/zip_encryption_method_supported.man | 2 +- .../man/zip_encryption_method_supported.mdoc | 2 +- core/deps/libzip/man/zip_error_clear.html | 4 +- core/deps/libzip/man/zip_error_clear.man | 4 +- core/deps/libzip/man/zip_error_clear.mdoc | 4 +- .../libzip/man/zip_error_code_system.html | 2 +- .../deps/libzip/man/zip_error_code_system.man | 2 +- .../libzip/man/zip_error_code_system.mdoc | 2 +- core/deps/libzip/man/zip_error_code_zip.html | 2 +- core/deps/libzip/man/zip_error_code_zip.man | 2 +- core/deps/libzip/man/zip_error_code_zip.mdoc | 2 +- core/deps/libzip/man/zip_error_fini.html | 2 +- core/deps/libzip/man/zip_error_fini.man | 2 +- core/deps/libzip/man/zip_error_fini.mdoc | 2 +- core/deps/libzip/man/zip_error_get.html | 2 +- core/deps/libzip/man/zip_error_get.man | 2 +- core/deps/libzip/man/zip_error_get.mdoc | 2 +- .../libzip/man/zip_error_get_sys_type.html | 2 +- .../libzip/man/zip_error_get_sys_type.man | 2 +- .../libzip/man/zip_error_get_sys_type.mdoc | 2 +- core/deps/libzip/man/zip_error_init.html | 2 +- core/deps/libzip/man/zip_error_init.man | 2 +- core/deps/libzip/man/zip_error_init.mdoc | 2 +- core/deps/libzip/man/zip_error_set.html | 11 +- core/deps/libzip/man/zip_error_set.man | 11 +- core/deps/libzip/man/zip_error_set.mdoc | 11 +- .../libzip/man/zip_error_set_from_source.mdoc | 69 + core/deps/libzip/man/zip_error_strerror.html | 2 +- core/deps/libzip/man/zip_error_strerror.man | 2 +- core/deps/libzip/man/zip_error_strerror.mdoc | 2 +- .../libzip/man/zip_error_system_type.html | 2 +- .../deps/libzip/man/zip_error_system_type.man | 2 +- .../libzip/man/zip_error_system_type.mdoc | 2 +- core/deps/libzip/man/zip_error_to_data.html | 2 +- core/deps/libzip/man/zip_error_to_data.man | 2 +- core/deps/libzip/man/zip_error_to_data.mdoc | 2 +- core/deps/libzip/man/zip_error_to_str.html | 2 +- core/deps/libzip/man/zip_error_to_str.man | 2 +- core/deps/libzip/man/zip_error_to_str.mdoc | 2 +- core/deps/libzip/man/zip_errors.html | 4 +- core/deps/libzip/man/zip_errors.man | 4 +- core/deps/libzip/man/zip_errors.mdoc | 4 +- core/deps/libzip/man/zip_fclose.html | 2 +- core/deps/libzip/man/zip_fclose.man | 2 +- core/deps/libzip/man/zip_fclose.mdoc | 2 +- core/deps/libzip/man/zip_fdopen.html | 8 +- core/deps/libzip/man/zip_fdopen.man | 10 +- core/deps/libzip/man/zip_fdopen.mdoc | 8 +- core/deps/libzip/man/zip_file_add.html | 4 +- core/deps/libzip/man/zip_file_add.man | 4 +- core/deps/libzip/man/zip_file_add.mdoc | 4 +- .../libzip/man/zip_file_attributes_init.html | 2 +- .../libzip/man/zip_file_attributes_init.man | 2 +- .../libzip/man/zip_file_attributes_init.mdoc | 2 +- .../man/zip_file_extra_field_delete.html | 2 +- .../man/zip_file_extra_field_delete.man | 2 +- .../man/zip_file_extra_field_delete.mdoc | 2 +- .../libzip/man/zip_file_extra_field_get.html | 2 +- .../libzip/man/zip_file_extra_field_get.man | 2 +- .../libzip/man/zip_file_extra_field_get.mdoc | 2 +- .../libzip/man/zip_file_extra_field_set.html | 2 +- .../libzip/man/zip_file_extra_field_set.man | 2 +- .../libzip/man/zip_file_extra_field_set.mdoc | 2 +- .../man/zip_file_extra_fields_count.html | 2 +- .../man/zip_file_extra_fields_count.man | 2 +- .../man/zip_file_extra_fields_count.mdoc | 2 +- .../deps/libzip/man/zip_file_get_comment.html | 4 +- core/deps/libzip/man/zip_file_get_comment.man | 4 +- .../deps/libzip/man/zip_file_get_comment.mdoc | 4 +- core/deps/libzip/man/zip_file_get_error.html | 2 +- core/deps/libzip/man/zip_file_get_error.man | 2 +- core/deps/libzip/man/zip_file_get_error.mdoc | 2 +- .../man/zip_file_get_external_attributes.html | 2 +- .../man/zip_file_get_external_attributes.man | 2 +- .../man/zip_file_get_external_attributes.mdoc | 2 +- core/deps/libzip/man/zip_file_rename.html | 4 +- core/deps/libzip/man/zip_file_rename.man | 4 +- core/deps/libzip/man/zip_file_rename.mdoc | 4 +- .../deps/libzip/man/zip_file_set_comment.html | 4 +- core/deps/libzip/man/zip_file_set_comment.man | 4 +- .../deps/libzip/man/zip_file_set_comment.mdoc | 4 +- .../libzip/man/zip_file_set_encryption.html | 4 +- .../libzip/man/zip_file_set_encryption.man | 4 +- .../libzip/man/zip_file_set_encryption.mdoc | 4 +- .../man/zip_file_set_external_attributes.html | 2 +- .../man/zip_file_set_external_attributes.man | 2 +- .../man/zip_file_set_external_attributes.mdoc | 2 +- core/deps/libzip/man/zip_file_set_mtime.html | 9 +- core/deps/libzip/man/zip_file_set_mtime.man | 10 +- core/deps/libzip/man/zip_file_set_mtime.mdoc | 8 +- core/deps/libzip/man/zip_file_strerror.html | 4 +- core/deps/libzip/man/zip_file_strerror.man | 4 +- core/deps/libzip/man/zip_file_strerror.mdoc | 4 +- core/deps/libzip/man/zip_fopen.html | 7 +- core/deps/libzip/man/zip_fopen.man | 7 +- core/deps/libzip/man/zip_fopen.mdoc | 7 +- core/deps/libzip/man/zip_fopen_encrypted.html | 4 +- core/deps/libzip/man/zip_fopen_encrypted.man | 4 +- core/deps/libzip/man/zip_fopen_encrypted.mdoc | 4 +- core/deps/libzip/man/zip_fread.html | 17 +- core/deps/libzip/man/zip_fread.man | 18 +- core/deps/libzip/man/zip_fread.mdoc | 18 +- core/deps/libzip/man/zip_fseek.html | 23 +- core/deps/libzip/man/zip_fseek.man | 32 +- core/deps/libzip/man/zip_fseek.mdoc | 27 +- core/deps/libzip/man/zip_ftell.html | 4 +- core/deps/libzip/man/zip_ftell.man | 4 +- core/deps/libzip/man/zip_ftell.mdoc | 4 +- .../libzip/man/zip_get_archive_comment.html | 4 +- .../libzip/man/zip_get_archive_comment.man | 4 +- .../libzip/man/zip_get_archive_comment.mdoc | 4 +- .../deps/libzip/man/zip_get_archive_flag.html | 19 +- core/deps/libzip/man/zip_get_archive_flag.man | 23 +- .../deps/libzip/man/zip_get_archive_flag.mdoc | 20 +- core/deps/libzip/man/zip_get_error.html | 2 +- core/deps/libzip/man/zip_get_error.man | 2 +- core/deps/libzip/man/zip_get_error.mdoc | 2 +- .../deps/libzip/man/zip_get_file_comment.html | 2 +- core/deps/libzip/man/zip_get_file_comment.man | 2 +- .../deps/libzip/man/zip_get_file_comment.mdoc | 2 +- core/deps/libzip/man/zip_get_name.html | 4 +- core/deps/libzip/man/zip_get_name.man | 4 +- core/deps/libzip/man/zip_get_name.mdoc | 4 +- core/deps/libzip/man/zip_get_num_entries.html | 21 +- core/deps/libzip/man/zip_get_num_entries.man | 18 +- core/deps/libzip/man/zip_get_num_entries.mdoc | 18 +- core/deps/libzip/man/zip_get_num_files.html | 2 +- core/deps/libzip/man/zip_get_num_files.man | 2 +- core/deps/libzip/man/zip_get_num_files.mdoc | 2 +- core/deps/libzip/man/zip_libzip_version.html | 2 +- core/deps/libzip/man/zip_libzip_version.man | 2 +- core/deps/libzip/man/zip_libzip_version.mdoc | 2 +- core/deps/libzip/man/zip_name_locate.html | 40 +- core/deps/libzip/man/zip_name_locate.man | 56 +- core/deps/libzip/man/zip_name_locate.mdoc | 45 +- core/deps/libzip/man/zip_open.html | 30 +- core/deps/libzip/man/zip_open.man | 34 +- core/deps/libzip/man/zip_open.mdoc | 28 +- ...p_register_cancel_callback_with_state.html | 119 + ...ip_register_cancel_callback_with_state.man | 90 + ...p_register_cancel_callback_with_state.mdoc | 84 + .../man/zip_register_progress_callback.html | 2 +- .../man/zip_register_progress_callback.man | 2 +- .../man/zip_register_progress_callback.mdoc | 2 +- ...register_progress_callback_with_state.html | 7 +- ..._register_progress_callback_with_state.man | 7 +- ...register_progress_callback_with_state.mdoc | 7 +- core/deps/libzip/man/zip_rename.html | 2 +- core/deps/libzip/man/zip_rename.man | 2 +- core/deps/libzip/man/zip_rename.mdoc | 2 +- .../libzip/man/zip_set_archive_comment.html | 4 +- .../libzip/man/zip_set_archive_comment.man | 4 +- .../libzip/man/zip_set_archive_comment.mdoc | 4 +- .../deps/libzip/man/zip_set_archive_flag.html | 22 +- core/deps/libzip/man/zip_set_archive_flag.man | 23 +- .../deps/libzip/man/zip_set_archive_flag.mdoc | 22 +- .../libzip/man/zip_set_default_password.html | 4 +- .../libzip/man/zip_set_default_password.man | 4 +- .../libzip/man/zip_set_default_password.mdoc | 4 +- .../deps/libzip/man/zip_set_file_comment.html | 2 +- core/deps/libzip/man/zip_set_file_comment.man | 2 +- .../deps/libzip/man/zip_set_file_comment.mdoc | 2 +- .../libzip/man/zip_set_file_compression.html | 28 +- .../libzip/man/zip_set_file_compression.man | 38 +- .../libzip/man/zip_set_file_compression.mdoc | 36 +- core/deps/libzip/man/zip_source.html | 8 +- core/deps/libzip/man/zip_source.man | 6 +- core/deps/libzip/man/zip_source.mdoc | 6 +- .../libzip/man/zip_source_begin_write.html | 2 +- .../libzip/man/zip_source_begin_write.man | 2 +- .../libzip/man/zip_source_begin_write.mdoc | 2 +- core/deps/libzip/man/zip_source_buffer.html | 4 +- core/deps/libzip/man/zip_source_buffer.man | 4 +- core/deps/libzip/man/zip_source_buffer.mdoc | 4 +- .../man/zip_source_buffer_fragment.html | 4 +- .../libzip/man/zip_source_buffer_fragment.man | 4 +- .../man/zip_source_buffer_fragment.mdoc | 4 +- core/deps/libzip/man/zip_source_close.html | 4 +- core/deps/libzip/man/zip_source_close.man | 4 +- core/deps/libzip/man/zip_source_close.mdoc | 4 +- .../libzip/man/zip_source_commit_write.html | 2 +- .../libzip/man/zip_source_commit_write.man | 2 +- .../libzip/man/zip_source_commit_write.mdoc | 2 +- core/deps/libzip/man/zip_source_error.html | 2 +- core/deps/libzip/man/zip_source_error.man | 2 +- core/deps/libzip/man/zip_source_error.mdoc | 2 +- core/deps/libzip/man/zip_source_file.html | 30 +- core/deps/libzip/man/zip_source_file.man | 49 +- core/deps/libzip/man/zip_source_file.mdoc | 49 +- core/deps/libzip/man/zip_source_filep.html | 17 +- core/deps/libzip/man/zip_source_filep.man | 21 +- core/deps/libzip/man/zip_source_filep.mdoc | 21 +- core/deps/libzip/man/zip_source_free.html | 2 +- core/deps/libzip/man/zip_source_free.man | 2 +- core/deps/libzip/man/zip_source_free.mdoc | 2 +- core/deps/libzip/man/zip_source_function.html | 28 +- core/deps/libzip/man/zip_source_function.man | 26 +- core/deps/libzip/man/zip_source_function.mdoc | 26 +- .../libzip/man/zip_source_is_deleted.html | 2 +- .../deps/libzip/man/zip_source_is_deleted.man | 2 +- .../libzip/man/zip_source_is_deleted.mdoc | 2 +- .../libzip/man/zip_source_is_seekable.html | 103 + .../libzip/man/zip_source_is_seekable.man | 70 + .../libzip/man/zip_source_is_seekable.mdoc | 65 + core/deps/libzip/man/zip_source_keep.html | 2 +- core/deps/libzip/man/zip_source_keep.man | 2 +- core/deps/libzip/man/zip_source_keep.mdoc | 2 +- core/deps/libzip/man/zip_source_layered.html | 215 ++ core/deps/libzip/man/zip_source_layered.man | 173 + core/deps/libzip/man/zip_source_layered.mdoc | 167 + .../man/zip_source_make_command_bitmap.html | 2 +- .../man/zip_source_make_command_bitmap.man | 2 +- .../man/zip_source_make_command_bitmap.mdoc | 2 +- core/deps/libzip/man/zip_source_open.html | 2 +- core/deps/libzip/man/zip_source_open.man | 2 +- core/deps/libzip/man/zip_source_open.mdoc | 2 +- .../man/zip_source_pass_to_lower_layer.mdoc | 64 + core/deps/libzip/man/zip_source_read.html | 13 +- core/deps/libzip/man/zip_source_read.man | 10 +- core/deps/libzip/man/zip_source_read.mdoc | 10 +- .../libzip/man/zip_source_rollback_write.html | 13 +- .../libzip/man/zip_source_rollback_write.man | 11 +- .../libzip/man/zip_source_rollback_write.mdoc | 11 +- core/deps/libzip/man/zip_source_seek.html | 5 +- core/deps/libzip/man/zip_source_seek.man | 5 +- core/deps/libzip/man/zip_source_seek.mdoc | 5 +- .../man/zip_source_seek_compute_offset.html | 2 +- .../man/zip_source_seek_compute_offset.man | 2 +- .../man/zip_source_seek_compute_offset.mdoc | 2 +- .../libzip/man/zip_source_seek_write.html | 2 +- .../deps/libzip/man/zip_source_seek_write.man | 2 +- .../libzip/man/zip_source_seek_write.mdoc | 2 +- core/deps/libzip/man/zip_source_stat.html | 4 +- core/deps/libzip/man/zip_source_stat.man | 4 +- core/deps/libzip/man/zip_source_stat.mdoc | 4 +- core/deps/libzip/man/zip_source_tell.html | 2 +- core/deps/libzip/man/zip_source_tell.man | 2 +- core/deps/libzip/man/zip_source_tell.mdoc | 2 +- .../libzip/man/zip_source_tell_write.html | 2 +- .../deps/libzip/man/zip_source_tell_write.man | 2 +- .../libzip/man/zip_source_tell_write.mdoc | 2 +- core/deps/libzip/man/zip_source_win32a.html | 14 +- core/deps/libzip/man/zip_source_win32a.man | 19 +- core/deps/libzip/man/zip_source_win32a.mdoc | 19 +- .../libzip/man/zip_source_win32handle.html | 4 +- .../libzip/man/zip_source_win32handle.man | 4 +- .../libzip/man/zip_source_win32handle.mdoc | 4 +- core/deps/libzip/man/zip_source_win32w.html | 15 +- core/deps/libzip/man/zip_source_win32w.man | 19 +- core/deps/libzip/man/zip_source_win32w.mdoc | 19 +- .../libzip/man/zip_source_window_create.html | 127 + .../libzip/man/zip_source_window_create.man | 104 + .../libzip/man/zip_source_window_create.mdoc | 99 + core/deps/libzip/man/zip_source_write.html | 2 +- core/deps/libzip/man/zip_source_write.man | 2 +- core/deps/libzip/man/zip_source_write.mdoc | 2 +- core/deps/libzip/man/zip_source_zip.html | 68 +- core/deps/libzip/man/zip_source_zip.man | 60 +- core/deps/libzip/man/zip_source_zip.mdoc | 51 +- core/deps/libzip/man/zip_source_zip_file.html | 168 + core/deps/libzip/man/zip_source_zip_file.man | 159 + core/deps/libzip/man/zip_source_zip_file.mdoc | 144 + core/deps/libzip/man/zip_stat.html | 2 +- core/deps/libzip/man/zip_stat.man | 2 +- core/deps/libzip/man/zip_stat.mdoc | 2 +- core/deps/libzip/man/zip_stat_init.html | 2 +- core/deps/libzip/man/zip_stat_init.man | 2 +- core/deps/libzip/man/zip_stat_init.mdoc | 2 +- core/deps/libzip/man/zip_unchange.html | 2 +- core/deps/libzip/man/zip_unchange.man | 2 +- core/deps/libzip/man/zip_unchange.mdoc | 2 +- core/deps/libzip/man/zip_unchange_all.html | 2 +- core/deps/libzip/man/zip_unchange_all.man | 2 +- core/deps/libzip/man/zip_unchange_all.mdoc | 2 +- .../deps/libzip/man/zip_unchange_archive.html | 2 +- core/deps/libzip/man/zip_unchange_archive.man | 2 +- .../deps/libzip/man/zip_unchange_archive.mdoc | 2 +- core/deps/libzip/man/zipcmp.html | 18 +- core/deps/libzip/man/zipcmp.man | 19 +- core/deps/libzip/man/zipcmp.mdoc | 17 +- core/deps/libzip/man/zipmerge.html | 21 +- core/deps/libzip/man/zipmerge.man | 20 +- core/deps/libzip/man/zipmerge.mdoc | 19 +- core/deps/libzip/man/ziptool.html | 37 +- core/deps/libzip/man/ziptool.man | 52 +- core/deps/libzip/man/ziptool.mdoc | 40 +- core/deps/libzip/regress/CMakeLists.txt | 106 + core/deps/libzip/regress/add_dir.test | 4 + core/deps/libzip/regress/add_from_buffer.test | 4 + core/deps/libzip/regress/add_from_file.test | 5 + .../regress/add_from_file_duplicate.test | 8 + .../add_from_file_twice_duplicate.test | 8 + .../regress/add_from_file_unchange.test | 4 + core/deps/libzip/regress/add_from_filep.c | 96 + core/deps/libzip/regress/add_from_filep.test | 6 + core/deps/libzip/regress/add_from_stdin.test | 7 + .../libzip/regress/add_from_zip_closed.test | 7 + .../libzip/regress/add_from_zip_deflated.test | 5 + .../regress/add_from_zip_deflated2.test | 5 + .../add_from_zip_partial_deflated.test | 5 + .../regress/add_from_zip_partial_stored.test | 5 + .../libzip/regress/add_from_zip_stored.test | 5 + core/deps/libzip/regress/add_stored.test | 4 + .../libzip/regress/add_stored_in_memory.test | 4 + core/deps/libzip/regress/bigstored.zh | Bin 0 -> 24632 bytes core/deps/libzip/regress/bigzero-zip.zip | Bin 0 -> 10490 bytes core/deps/libzip/regress/bogus.zip | 1 + core/deps/libzip/regress/broken.zip | Bin 0 -> 75091 bytes .../libzip/regress/buffer-fragment-read.test | 7 + .../libzip/regress/buffer-fragment-write.test | 4 + core/deps/libzip/regress/can_clone_file.c | 127 + core/deps/libzip/regress/cancel_45.test | 12 + core/deps/libzip/regress/cancel_90.test | 14 + .../changing-size-decreases-fixed.test | 12 + .../regress/changing-size-decreases.test | 12 + .../changing-size-increases-fixed.test | 8 + .../changing-size-increases-unchecked.test | 8 + .../regress/changing-size-increases.test | 6 + .../libzip/regress/changing-size-muchl.zip | Bin 0 -> 308 bytes .../libzip/regress/changing-size-muchlo.zip | Bin 0 -> 309 bytes .../regress/changing-size-muchlonger.zip | Bin 0 -> 314 bytes core/deps/libzip/regress/changing-size.zip | Bin 0 -> 221 bytes .../libzip/regress/check_torrentzip_fail.test | 7 + .../regress/check_torrentzip_modified.test | 7 + .../regress/check_torrentzip_success.test | 7 + core/deps/libzip/regress/cleanup.cmake | 7 + .../deps/libzip/regress/clone-buffer-add.test | 4 + .../libzip/regress/clone-buffer-delete.test | 4 + .../libzip/regress/clone-buffer-replace.test | 4 + core/deps/libzip/regress/clone-fs-add.test | 5 + core/deps/libzip/regress/clone-fs-delete.test | 5 + .../deps/libzip/regress/clone-fs-replace.test | 5 + core/deps/libzip/regress/cm-default.test | 5 + core/deps/libzip/regress/cm-default.zip | Bin 0 -> 8702 bytes .../libzip/regress/convert_to_torrentzip.test | 4 + .../regress/convert_to_torrentzip_ef.test | 4 + core/deps/libzip/regress/count_entries.test | 7 + .../libzip/regress/create_empty_keep.test | 4 + .../decrypt-correct-password-aes128.test | 8 + .../decrypt-correct-password-aes192.test | 8 + .../decrypt-correct-password-aes256.test | 8 + .../decrypt-correct-password-pkware-2.test | 11 + .../decrypt-correct-password-pkware.test | 7 + .../regress/decrypt-empty-file-pkware.test | 6 + .../regress/decrypt-no-password-aes256.test | 8 + .../decrypt-wrong-password-aes128.test | 8 + .../decrypt-wrong-password-aes192.test | 8 + .../decrypt-wrong-password-aes256.test | 8 + .../decrypt-wrong-password-pkware-2.test | 9 + .../decrypt-wrong-password-pkware.test | 7 + core/deps/libzip/regress/delete_add_same.test | 5 + core/deps/libzip/regress/delete_invalid.test | 7 + core/deps/libzip/regress/delete_last.test | 4 + .../deps/libzip/regress/delete_last_keep.test | 4 + .../libzip/regress/delete_multiple_last.test | 4 + .../regress/delete_multiple_partial.test | 4 + .../libzip/regress/delete_renamed_rename.test | 5 + core/deps/libzip/regress/empty-pkware.zip | Bin 0 -> 112 bytes core/deps/libzip/regress/encrypt-1234.zip | Bin 0 -> 380 bytes .../regress/encrypt-aes128-noentropy.zip | Bin 0 -> 260 bytes core/deps/libzip/regress/encrypt-aes128.zip | Bin 0 -> 260 bytes .../regress/encrypt-aes192-noentropy.zip | Bin 0 -> 264 bytes core/deps/libzip/regress/encrypt-aes192.zip | Bin 0 -> 316 bytes .../regress/encrypt-aes256-noentropy.zip | Bin 0 -> 268 bytes core/deps/libzip/regress/encrypt-aes256.zip | Bin 0 -> 320 bytes core/deps/libzip/regress/encrypt-none.zip | Bin 0 -> 218 bytes .../regress/encrypt-pkware-noentropy-2.zip | Bin 0 -> 246 bytes .../regress/encrypt-pkware-noentropy.zip | Bin 0 -> 246 bytes core/deps/libzip/regress/encrypt.test | 25 + core/deps/libzip/regress/encrypt.zip | Bin 0 -> 306 bytes .../libzip/regress/encrypt_plus_extra.zip | Bin 0 -> 348 bytes .../regress/encrypt_plus_extra_modified_c.zip | Bin 0 -> 332 bytes .../regress/encrypt_plus_extra_modified_l.zip | Bin 0 -> 332 bytes .../regress/encryption-nonrandom-aes128.test | 7 + .../regress/encryption-nonrandom-aes192.test | 7 + .../regress/encryption-nonrandom-aes256.test | 7 + .../encryption-nonrandom-pkware-2.test | 7 + .../regress/encryption-nonrandom-pkware.test | 7 + .../libzip/regress/encryption-remove.test | 5 + core/deps/libzip/regress/encryption-stat.test | 25 + core/deps/libzip/regress/extra_add.test | 9 + .../libzip/regress/extra_add_multiple.test | 10 + core/deps/libzip/regress/extra_count.test | 12 + .../libzip/regress/extra_count_by_id.test | 24 + .../regress/extra_count_ignore_zip64.test | 9 + core/deps/libzip/regress/extra_delete.test | 16 + .../libzip/regress/extra_delete_by_id.test | 8 + .../libzip/regress/extra_field_align.test | 35 + .../libzip/regress/extra_field_align_1-0.zip | Bin 0 -> 110 bytes .../regress/extra_field_align_1-ef_00.zip | Bin 0 -> 123 bytes .../regress/extra_field_align_1-ef_ff.zip | Bin 0 -> 123 bytes .../libzip/regress/extra_field_align_1-ff.zip | Bin 0 -> 110 bytes .../libzip/regress/extra_field_align_2-0.zip | Bin 0 -> 110 bytes .../regress/extra_field_align_2-ef_00.zip | Bin 0 -> 123 bytes .../regress/extra_field_align_2-ef_ff.zip | Bin 0 -> 123 bytes .../libzip/regress/extra_field_align_2-ff.zip | Bin 0 -> 110 bytes .../libzip/regress/extra_field_align_3-0.zip | Bin 0 -> 110 bytes .../regress/extra_field_align_3-ef_00.zip | Bin 0 -> 123 bytes .../regress/extra_field_align_3-ef_ff.zip | Bin 0 -> 123 bytes .../libzip/regress/extra_field_align_3-ff.zip | Bin 0 -> 110 bytes .../libzip/regress/extra_field_align_4-ff.zip | Bin 0 -> 113 bytes core/deps/libzip/regress/extra_get.test | 13 + core/deps/libzip/regress/extra_get_by_id.test | 13 + core/deps/libzip/regress/extra_set.test | 16 + .../libzip/regress/extra_set_modify_c.test | 18 + .../libzip/regress/extra_set_modify_l.test | 18 + core/deps/libzip/regress/fdopen_ok.test | 17 + .../regress/file_comment_encmismatch.test | 7 + .../libzip/regress/filename_duplicate.zip | Bin 0 -> 206 bytes .../regress/filename_duplicate_empty.zip | Bin 0 -> 186 bytes core/deps/libzip/regress/filename_empty.zip | Bin 0 -> 98 bytes core/deps/libzip/regress/fileorder.zip | Bin 0 -> 204 bytes .../regress/firstsecond-split-deflated.zip | Bin 0 -> 219 bytes .../regress/firstsecond-split-stored.zip | Bin 0 -> 215 bytes core/deps/libzip/regress/firstsecond.zip | Bin 0 -> 319 bytes core/deps/libzip/regress/foo-stored.zip | Bin 0 -> 107 bytes core/deps/libzip/regress/fopen_multiple.test | 9 + .../libzip/regress/fopen_multiple_reopen.test | 9 + core/deps/libzip/regress/fopen_unchanged.c | 90 + core/deps/libzip/regress/fopen_unchanged.test | 7 + core/deps/libzip/regress/fread.c | 212 ++ core/deps/libzip/regress/fread.test | 5 + core/deps/libzip/regress/fseek.c | 104 + core/deps/libzip/regress/fseek_deflated.test | 8 + core/deps/libzip/regress/fseek_fail.test | 8 + core/deps/libzip/regress/fseek_ok.test | 8 + core/deps/libzip/regress/fuzz_main.c | 50 + core/deps/libzip/regress/gap-add.zip | Bin 0 -> 451 bytes core/deps/libzip/regress/gap-delete.zip | Bin 0 -> 250 bytes core/deps/libzip/regress/gap-replace.zip | Bin 0 -> 351 bytes core/deps/libzip/regress/gap.zip | Bin 0 -> 358 bytes core/deps/libzip/regress/get_comment.test | 16 + core/deps/libzip/regress/hole.c | 190 ++ .../regress/incons-archive-comment-longer.zip | Bin 0 -> 153 bytes .../incons-archive-comment-shorter.zip | Bin 0 -> 203 bytes core/deps/libzip/regress/incons-cdoffset.zip | Bin 0 -> 153 bytes .../incons-central-compression-method.zip | Bin 0 -> 153 bytes ...ncons-central-compsize-larger-toolarge.zip | Bin 0 -> 153 bytes .../incons-central-compsize-larger.zip | Bin 0 -> 153 bytes .../incons-central-compsize-smaller.zip | Bin 0 -> 173 bytes .../libzip/regress/incons-central-crc.zip | Bin 0 -> 153 bytes .../libzip/regress/incons-central-date.zip | Bin 0 -> 131 bytes .../incons-central-file-comment-longer.zip | Bin 0 -> 153 bytes .../incons-central-file-comment-shorter.zip | Bin 0 -> 258 bytes .../regress/incons-central-magic-bad.zip | Bin 0 -> 153 bytes .../regress/incons-central-magic-bad2.zip | Bin 0 -> 153 bytes .../regress/incons-central-size-larger.zip | Bin 0 -> 153 bytes core/deps/libzip/regress/incons-data.zip | Bin 0 -> 153 bytes .../regress/incons-ef-central-size-wrong.zip | Bin 0 -> 151 bytes .../regress/incons-ef-local-id-size.zip | Bin 0 -> 153 bytes .../libzip/regress/incons-ef-local-id.zip | Bin 0 -> 153 bytes .../libzip/regress/incons-ef-local-size.zip | Bin 0 -> 153 bytes .../libzip/regress/incons-eocd-magic-bad.zip | Bin 0 -> 153 bytes .../libzip/regress/incons-file-count-high.zip | Bin 0 -> 153 bytes .../libzip/regress/incons-file-count-low.zip | Bin 0 -> 304 bytes .../regress/incons-file-count-overflow.zip | Bin 0 -> 198 bytes .../incons-local-compression-method.zip | Bin 0 -> 153 bytes .../regress/incons-local-compsize-larger.zip | Bin 0 -> 153 bytes .../regress/incons-local-compsize-smaller.zip | Bin 0 -> 173 bytes core/deps/libzip/regress/incons-local-crc.zip | Bin 0 -> 153 bytes .../regress/incons-local-filename-long.zip | Bin 0 -> 153 bytes .../regress/incons-local-filename-missing.zip | Bin 0 -> 152 bytes .../regress/incons-local-filename-short.zip | Bin 0 -> 153 bytes .../libzip/regress/incons-local-filename.zip | Bin 0 -> 153 bytes .../libzip/regress/incons-local-magic-bad.zip | Bin 0 -> 153 bytes .../regress/incons-local-size-larger.zip | Bin 0 -> 153 bytes core/deps/libzip/regress/junk-at-end.zip | Bin 0 -> 416 bytes core/deps/libzip/regress/junk-at-start.zip | Bin 0 -> 416 bytes core/deps/libzip/regress/junk_at_end.test | 7 + core/deps/libzip/regress/junk_at_start.test | 7 + core/deps/libzip/regress/large-uncompressible | Bin 0 -> 8200 bytes core/deps/libzip/regress/liboverride-test.c | 108 + core/deps/libzip/regress/liboverride.c | 48 + core/deps/libzip/regress/lzma-no-eos.zip | Bin 0 -> 343 bytes core/deps/libzip/regress/malloc.c | 162 + core/deps/libzip/regress/manyfiles-zip.zip | Bin 0 -> 3434201 bytes core/deps/libzip/regress/multidisk.zip | Bin 0 -> 122 bytes .../libzip/regress/name_locate-cp437.test | 13 + .../deps/libzip/regress/name_locate-utf8.test | 16 + core/deps/libzip/regress/name_locate.test | 31 + core/deps/libzip/regress/nihtest.conf.in | 18 +- core/deps/libzip/regress/nonrandomopen.c | 44 + core/deps/libzip/regress/nonrandomopentest.c | 57 + .../libzip/regress/open_cons_extrabytes.test | 11 + core/deps/libzip/regress/open_empty.test | 8 + core/deps/libzip/regress/open_empty_2.test | 11 + core/deps/libzip/regress/open_extrabytes.test | 8 + core/deps/libzip/regress/open_file_count.test | 15 + .../regress/open_filename_duplicate.test | 8 + .../open_filename_duplicate_consistency.test | 11 + .../open_filename_duplicate_empty.test | 8 + ..._filename_duplicate_empty_consistency.test | 11 + .../libzip/regress/open_filename_empty.test | 8 + core/deps/libzip/regress/open_incons.test | 77 + core/deps/libzip/regress/open_many_fail.test | 15 + core/deps/libzip/regress/open_many_ok.test | 14 + core/deps/libzip/regress/open_multidisk.test | 11 + .../libzip/regress/open_new_but_exists.test | 11 + core/deps/libzip/regress/open_new_ok.test | 7 + core/deps/libzip/regress/open_nonarchive.test | 11 + core/deps/libzip/regress/open_nosuchfile.test | 10 + core/deps/libzip/regress/open_ok.test | 8 + core/deps/libzip/regress/open_too_short.test | 11 + core/deps/libzip/regress/open_truncate.test | 8 + core/deps/libzip/regress/open_zip64_3mf.test | 8 + core/deps/libzip/regress/open_zip64_ok.test | 8 + core/deps/libzip/regress/ossfuzz.sh | 35 + core/deps/libzip/regress/preload.test | 4 + core/deps/libzip/regress/progress.test | 12 + core/deps/libzip/regress/read_seek_read.test | 9 + core/deps/libzip/regress/rename_ascii.test | 4 + core/deps/libzip/regress/rename_cp437.test | 4 + core/deps/libzip/regress/rename_deleted.test | 7 + core/deps/libzip/regress/rename_fail.test | 7 + core/deps/libzip/regress/rename_ok.test | 4 + core/deps/libzip/regress/rename_ok.zip | Bin 0 -> 709 bytes core/deps/libzip/regress/rename_utf8.test | 7 + .../regress/rename_utf8_encmismatch.test | 7 + core/deps/libzip/regress/reopen.test | 9 + core/deps/libzip/regress/reopen_partial.test | 8 + .../libzip/regress/reopen_partial_rest.test | 9 + core/deps/libzip/regress/runtest.in | 60 - core/deps/libzip/regress/set_comment_all.test | 4 + .../libzip/regress/set_comment_localonly.test | 4 + .../regress/set_comment_removeglobal.test | 4 + .../libzip/regress/set_comment_revert.test | 4 + .../set_compression_bzip2_to_deflate.test | 5 + .../set_compression_deflate_to_bzip2.test | 5 + .../set_compression_deflate_to_deflate.test | 4 + .../set_compression_deflate_to_store.test | 4 + .../set_compression_lzma_no_eos_to_store.test | 5 + .../set_compression_lzma_to_store.test | 5 + .../set_compression_store_to_bzip2.test | 5 + .../set_compression_store_to_deflate.test | 4 + .../set_compression_store_to_lzma.test | 5 + .../set_compression_store_to_store.test | 4 + .../regress/set_compression_store_to_xz.test | 5 + .../set_compression_store_to_zstd.test | 5 + .../regress/set_compression_unknown.test | 7 + .../regress/set_compression_xz_to_store.test | 5 + .../set_compression_zstd_to_store.test | 5 + .../deps/libzip/regress/set_file_dostime.test | 4 + core/deps/libzip/regress/set_file_mtime.test | 4 + .../libzip/regress/set_file_mtime_pkware.test | 7 + core/deps/libzip/regress/short | 1 + core/deps/libzip/regress/source_hole.c | 577 ++++ .../regress/stat_index_cp437_guess.test | 150 + .../libzip/regress/stat_index_cp437_raw.test | 150 + .../regress/stat_index_cp437_strict.test | 150 + .../libzip/regress/stat_index_fileorder.test | 24 + .../libzip/regress/stat_index_streamed.test | 15 + .../regress/stat_index_streamed_zip64.test | 15 + .../libzip/regress/stat_index_utf8_guess.test | 15 + .../libzip/regress/stat_index_utf8_raw.test | 15 + .../regress/stat_index_utf8_strict.test | 16 + .../stat_index_utf8_unmarked_strict.test | 16 + .../deps/libzip/regress/stat_index_zip64.test | 15 + core/deps/libzip/regress/stored-no-eos.zip | Bin 0 -> 348 bytes core/deps/libzip/regress/streamed-zip64.zip | Bin 0 -> 148 bytes core/deps/libzip/regress/streamed.zip | Bin 0 -> 120 bytes .../regress/test-cp437-comment-utf-8.zip | Bin 0 -> 2619 bytes .../regress/test-cp437-fc-utf-8-filename.zip | Bin 0 -> 236 bytes core/deps/libzip/regress/test-cp437-fc.zip | Bin 0 -> 186 bytes core/deps/libzip/regress/test-cp437.zip | Bin 0 -> 2582 bytes .../libzip/regress/test-utf8-unmarked.zip | Bin 0 -> 210 bytes core/deps/libzip/regress/test-utf8.zip | Bin 0 -> 210 bytes core/deps/libzip/regress/test.zip | Bin 0 -> 412 bytes core/deps/libzip/regress/test2.zip | Bin 0 -> 126 bytes .../libzip/regress/test_open_multiple.zip | Bin 0 -> 152 bytes core/deps/libzip/regress/testbuffer.zip | Bin 0 -> 180 bytes .../deps/libzip/regress/testbuffer_reopen.zip | Bin 0 -> 247 bytes core/deps/libzip/regress/testbzip2.zip | Bin 0 -> 175 bytes core/deps/libzip/regress/testchanged.zip | Bin 0 -> 728 bytes core/deps/libzip/regress/testchangedlocal.zip | Bin 0 -> 714 bytes core/deps/libzip/regress/testcomment.zip | Bin 0 -> 703 bytes core/deps/libzip/regress/testcomment13.zip | Bin 0 -> 383 bytes .../libzip/regress/testcommentremoved.zip | Bin 0 -> 640 bytes core/deps/libzip/regress/testdeflated.zip | Bin 0 -> 145 bytes core/deps/libzip/regress/testdeflated2.zip | Bin 0 -> 270 bytes core/deps/libzip/regress/testdir.zip | Bin 0 -> 222 bytes core/deps/libzip/regress/testempty.zip | Bin 0 -> 22 bytes core/deps/libzip/regress/testextrabytes.zip | Bin 0 -> 160 bytes core/deps/libzip/regress/testfile-UTF8.zip | Bin 0 -> 126 bytes core/deps/libzip/regress/testfile-cp437.zip | Bin 0 -> 130 bytes core/deps/libzip/regress/testfile-ef.zip | Bin 0 -> 174 bytes core/deps/libzip/regress/testfile-lzma.zip | Bin 0 -> 161 bytes .../libzip/regress/testfile-plus-extra.zip | Bin 0 -> 164 bytes .../libzip/regress/testfile-stored-dos.zip | Bin 0 -> 192 bytes .../regress/testfile-torrentzip-modified.zip | Bin 0 -> 146 bytes .../libzip/regress/testfile-torrentzip.zip | Bin 0 -> 146 bytes core/deps/libzip/regress/testfile-xz.zip | Bin 0 -> 200 bytes core/deps/libzip/regress/testfile-zstd.zip | Bin 0 -> 160 bytes core/deps/libzip/regress/testfile.txt | 0 core/deps/libzip/regress/testfile.zip | Bin 0 -> 122 bytes core/deps/libzip/regress/testfile0.zip | Bin 0 -> 122 bytes core/deps/libzip/regress/testfile2014.zip | Bin 0 -> 122 bytes core/deps/libzip/regress/teststdin.zip | Bin 0 -> 200 bytes core/deps/libzip/regress/teststored.zip | Bin 0 -> 188 bytes .../libzip/regress/truncate_empty_keep.test | 4 + core/deps/libzip/regress/tryopen.c | 115 + .../regress/unchange-delete-namelocate.test | 7 + .../regress/utf-8-standardization-input.zip | Bin 0 -> 285 bytes .../regress/utf-8-standardization-output.zip | Bin 0 -> 226 bytes .../libzip/regress/utf-8-standardization.test | 4 + .../libzip/regress/want_torrentzip_stat.test | 32 + .../regress/zip-in-archive-comment.test | 15 + .../libzip/regress/zip-in-archive-comment.zip | Bin 0 -> 320 bytes core/deps/libzip/regress/zip64-3mf.zip | Bin 0 -> 198 bytes core/deps/libzip/regress/zip64.zip | Bin 0 -> 198 bytes core/deps/libzip/regress/zip64_creation.test | 4 + .../libzip/regress/zip64_stored_creation.test | 4 + core/deps/libzip/regress/zip_read_fuzzer.cc | 48 + core/deps/libzip/regress/zip_read_fuzzer.dict | 3 + core/deps/libzip/regress/zipcmp_zip_dir.test | 17 + core/deps/libzip/regress/zipcmp_zip_dir.zip | Bin 0 -> 483 bytes core/deps/libzip/regress/ziptool_regress.c | 661 ++++ core/deps/libzip/src/CMakeLists.txt | 14 + core/deps/libzip/src/diff_output.c | 106 + core/deps/libzip/src/diff_output.h | 28 + core/deps/libzip/src/getopt.c | 110 + core/deps/libzip/src/getopt.h | 51 + core/deps/libzip/src/zipcmp.c | 880 +++++ core/deps/libzip/src/zipmerge.c | 336 ++ core/deps/libzip/src/ziptool.c | 1161 +++++++ core/deps/libzip/vstudio/readme.txt | 77 - core/deps/libzip/vstudio/vsbuild.cmd | 186 -- .../libzip/vstudio/zlib/unpack_zlib_here.txt | 1 - 850 files changed, 14720 insertions(+), 6247 deletions(-) create mode 100644 core/deps/libzip/.clang-format create mode 100644 core/deps/libzip/.github/ISSUE_TEMPLATE/bug-report.md create mode 100644 core/deps/libzip/.github/ISSUE_TEMPLATE/compile-error.md create mode 100644 core/deps/libzip/.github/ISSUE_TEMPLATE/feature-request.md create mode 100644 core/deps/libzip/.github/workflows/CIFuzz.yml create mode 100644 core/deps/libzip/.github/workflows/build.yml create mode 100644 core/deps/libzip/.github/workflows/codeql-analysis.yml create mode 100644 core/deps/libzip/SECURITY.md create mode 100644 core/deps/libzip/appveyor.yml delete mode 100644 core/deps/libzip/cmake/FindZstd.cmake create mode 100644 core/deps/libzip/cmake/Findzstd.cmake create mode 100644 core/deps/libzip/cmake/GenerateZipErrorStrings.cmake delete mode 100644 core/deps/libzip/developer-xcode/Info.plist delete mode 100644 core/deps/libzip/developer-xcode/README Xcode Project.md delete mode 100644 core/deps/libzip/developer-xcode/config.h delete mode 100644 core/deps/libzip/developer-xcode/extract-version.sh delete mode 100644 core/deps/libzip/developer-xcode/libzip.xcodeproj/project.pbxproj delete mode 100644 core/deps/libzip/developer-xcode/libzip.xcodeproj/project.xcworkspace/contents.xcworkspacedata delete mode 100644 core/deps/libzip/developer-xcode/libzip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist delete mode 100644 core/deps/libzip/developer-xcode/mkconfig-h.sh delete mode 100644 core/deps/libzip/developer-xcode/zip_err_str.c delete mode 100644 core/deps/libzip/developer-xcode/zipconf.h create mode 100644 core/deps/libzip/examples/add-compressed-data.c create mode 100644 core/deps/libzip/examples/autoclose-archive.c delete mode 100644 core/deps/libzip/lib/zip_err_str.c rename core/deps/libzip/lib/{zip_mkstempm.c => zip_source_pass_to_lower_layer.c} (53%) delete mode 100644 core/deps/libzip/man/fix-man-links.sh delete mode 100644 core/deps/libzip/man/make_zip_errors.sh delete mode 100644 core/deps/libzip/man/mkdocset.pl delete mode 100644 core/deps/libzip/man/nih-man.css create mode 100644 core/deps/libzip/man/zip_error_set_from_source.mdoc create mode 100644 core/deps/libzip/man/zip_register_cancel_callback_with_state.html create mode 100644 core/deps/libzip/man/zip_register_cancel_callback_with_state.man create mode 100644 core/deps/libzip/man/zip_register_cancel_callback_with_state.mdoc create mode 100644 core/deps/libzip/man/zip_source_is_seekable.html create mode 100644 core/deps/libzip/man/zip_source_is_seekable.man create mode 100644 core/deps/libzip/man/zip_source_is_seekable.mdoc create mode 100644 core/deps/libzip/man/zip_source_layered.html create mode 100644 core/deps/libzip/man/zip_source_layered.man create mode 100644 core/deps/libzip/man/zip_source_layered.mdoc create mode 100644 core/deps/libzip/man/zip_source_pass_to_lower_layer.mdoc create mode 100644 core/deps/libzip/man/zip_source_window_create.html create mode 100644 core/deps/libzip/man/zip_source_window_create.man create mode 100644 core/deps/libzip/man/zip_source_window_create.mdoc create mode 100644 core/deps/libzip/man/zip_source_zip_file.html create mode 100644 core/deps/libzip/man/zip_source_zip_file.man create mode 100644 core/deps/libzip/man/zip_source_zip_file.mdoc create mode 100644 core/deps/libzip/regress/CMakeLists.txt create mode 100644 core/deps/libzip/regress/add_dir.test create mode 100644 core/deps/libzip/regress/add_from_buffer.test create mode 100644 core/deps/libzip/regress/add_from_file.test create mode 100644 core/deps/libzip/regress/add_from_file_duplicate.test create mode 100644 core/deps/libzip/regress/add_from_file_twice_duplicate.test create mode 100644 core/deps/libzip/regress/add_from_file_unchange.test create mode 100644 core/deps/libzip/regress/add_from_filep.c create mode 100644 core/deps/libzip/regress/add_from_filep.test create mode 100644 core/deps/libzip/regress/add_from_stdin.test create mode 100644 core/deps/libzip/regress/add_from_zip_closed.test create mode 100644 core/deps/libzip/regress/add_from_zip_deflated.test create mode 100644 core/deps/libzip/regress/add_from_zip_deflated2.test create mode 100644 core/deps/libzip/regress/add_from_zip_partial_deflated.test create mode 100644 core/deps/libzip/regress/add_from_zip_partial_stored.test create mode 100644 core/deps/libzip/regress/add_from_zip_stored.test create mode 100644 core/deps/libzip/regress/add_stored.test create mode 100644 core/deps/libzip/regress/add_stored_in_memory.test create mode 100644 core/deps/libzip/regress/bigstored.zh create mode 100644 core/deps/libzip/regress/bigzero-zip.zip create mode 100644 core/deps/libzip/regress/bogus.zip create mode 100644 core/deps/libzip/regress/broken.zip create mode 100644 core/deps/libzip/regress/buffer-fragment-read.test create mode 100644 core/deps/libzip/regress/buffer-fragment-write.test create mode 100644 core/deps/libzip/regress/can_clone_file.c create mode 100644 core/deps/libzip/regress/cancel_45.test create mode 100644 core/deps/libzip/regress/cancel_90.test create mode 100644 core/deps/libzip/regress/changing-size-decreases-fixed.test create mode 100644 core/deps/libzip/regress/changing-size-decreases.test create mode 100644 core/deps/libzip/regress/changing-size-increases-fixed.test create mode 100644 core/deps/libzip/regress/changing-size-increases-unchecked.test create mode 100644 core/deps/libzip/regress/changing-size-increases.test create mode 100644 core/deps/libzip/regress/changing-size-muchl.zip create mode 100644 core/deps/libzip/regress/changing-size-muchlo.zip create mode 100644 core/deps/libzip/regress/changing-size-muchlonger.zip create mode 100644 core/deps/libzip/regress/changing-size.zip create mode 100644 core/deps/libzip/regress/check_torrentzip_fail.test create mode 100644 core/deps/libzip/regress/check_torrentzip_modified.test create mode 100644 core/deps/libzip/regress/check_torrentzip_success.test create mode 100644 core/deps/libzip/regress/cleanup.cmake create mode 100644 core/deps/libzip/regress/clone-buffer-add.test create mode 100644 core/deps/libzip/regress/clone-buffer-delete.test create mode 100644 core/deps/libzip/regress/clone-buffer-replace.test create mode 100644 core/deps/libzip/regress/clone-fs-add.test create mode 100644 core/deps/libzip/regress/clone-fs-delete.test create mode 100644 core/deps/libzip/regress/clone-fs-replace.test create mode 100644 core/deps/libzip/regress/cm-default.test create mode 100644 core/deps/libzip/regress/cm-default.zip create mode 100644 core/deps/libzip/regress/convert_to_torrentzip.test create mode 100644 core/deps/libzip/regress/convert_to_torrentzip_ef.test create mode 100644 core/deps/libzip/regress/count_entries.test create mode 100644 core/deps/libzip/regress/create_empty_keep.test create mode 100644 core/deps/libzip/regress/decrypt-correct-password-aes128.test create mode 100644 core/deps/libzip/regress/decrypt-correct-password-aes192.test create mode 100644 core/deps/libzip/regress/decrypt-correct-password-aes256.test create mode 100644 core/deps/libzip/regress/decrypt-correct-password-pkware-2.test create mode 100644 core/deps/libzip/regress/decrypt-correct-password-pkware.test create mode 100644 core/deps/libzip/regress/decrypt-empty-file-pkware.test create mode 100644 core/deps/libzip/regress/decrypt-no-password-aes256.test create mode 100644 core/deps/libzip/regress/decrypt-wrong-password-aes128.test create mode 100644 core/deps/libzip/regress/decrypt-wrong-password-aes192.test create mode 100644 core/deps/libzip/regress/decrypt-wrong-password-aes256.test create mode 100644 core/deps/libzip/regress/decrypt-wrong-password-pkware-2.test create mode 100644 core/deps/libzip/regress/decrypt-wrong-password-pkware.test create mode 100644 core/deps/libzip/regress/delete_add_same.test create mode 100644 core/deps/libzip/regress/delete_invalid.test create mode 100644 core/deps/libzip/regress/delete_last.test create mode 100644 core/deps/libzip/regress/delete_last_keep.test create mode 100644 core/deps/libzip/regress/delete_multiple_last.test create mode 100644 core/deps/libzip/regress/delete_multiple_partial.test create mode 100644 core/deps/libzip/regress/delete_renamed_rename.test create mode 100644 core/deps/libzip/regress/empty-pkware.zip create mode 100644 core/deps/libzip/regress/encrypt-1234.zip create mode 100644 core/deps/libzip/regress/encrypt-aes128-noentropy.zip create mode 100644 core/deps/libzip/regress/encrypt-aes128.zip create mode 100644 core/deps/libzip/regress/encrypt-aes192-noentropy.zip create mode 100644 core/deps/libzip/regress/encrypt-aes192.zip create mode 100644 core/deps/libzip/regress/encrypt-aes256-noentropy.zip create mode 100644 core/deps/libzip/regress/encrypt-aes256.zip create mode 100644 core/deps/libzip/regress/encrypt-none.zip create mode 100644 core/deps/libzip/regress/encrypt-pkware-noentropy-2.zip create mode 100644 core/deps/libzip/regress/encrypt-pkware-noentropy.zip create mode 100644 core/deps/libzip/regress/encrypt.test create mode 100644 core/deps/libzip/regress/encrypt.zip create mode 100644 core/deps/libzip/regress/encrypt_plus_extra.zip create mode 100644 core/deps/libzip/regress/encrypt_plus_extra_modified_c.zip create mode 100644 core/deps/libzip/regress/encrypt_plus_extra_modified_l.zip create mode 100644 core/deps/libzip/regress/encryption-nonrandom-aes128.test create mode 100644 core/deps/libzip/regress/encryption-nonrandom-aes192.test create mode 100644 core/deps/libzip/regress/encryption-nonrandom-aes256.test create mode 100644 core/deps/libzip/regress/encryption-nonrandom-pkware-2.test create mode 100644 core/deps/libzip/regress/encryption-nonrandom-pkware.test create mode 100644 core/deps/libzip/regress/encryption-remove.test create mode 100644 core/deps/libzip/regress/encryption-stat.test create mode 100644 core/deps/libzip/regress/extra_add.test create mode 100644 core/deps/libzip/regress/extra_add_multiple.test create mode 100644 core/deps/libzip/regress/extra_count.test create mode 100644 core/deps/libzip/regress/extra_count_by_id.test create mode 100644 core/deps/libzip/regress/extra_count_ignore_zip64.test create mode 100644 core/deps/libzip/regress/extra_delete.test create mode 100644 core/deps/libzip/regress/extra_delete_by_id.test create mode 100644 core/deps/libzip/regress/extra_field_align.test create mode 100644 core/deps/libzip/regress/extra_field_align_1-0.zip create mode 100644 core/deps/libzip/regress/extra_field_align_1-ef_00.zip create mode 100644 core/deps/libzip/regress/extra_field_align_1-ef_ff.zip create mode 100644 core/deps/libzip/regress/extra_field_align_1-ff.zip create mode 100644 core/deps/libzip/regress/extra_field_align_2-0.zip create mode 100644 core/deps/libzip/regress/extra_field_align_2-ef_00.zip create mode 100644 core/deps/libzip/regress/extra_field_align_2-ef_ff.zip create mode 100644 core/deps/libzip/regress/extra_field_align_2-ff.zip create mode 100644 core/deps/libzip/regress/extra_field_align_3-0.zip create mode 100644 core/deps/libzip/regress/extra_field_align_3-ef_00.zip create mode 100644 core/deps/libzip/regress/extra_field_align_3-ef_ff.zip create mode 100644 core/deps/libzip/regress/extra_field_align_3-ff.zip create mode 100644 core/deps/libzip/regress/extra_field_align_4-ff.zip create mode 100644 core/deps/libzip/regress/extra_get.test create mode 100644 core/deps/libzip/regress/extra_get_by_id.test create mode 100644 core/deps/libzip/regress/extra_set.test create mode 100644 core/deps/libzip/regress/extra_set_modify_c.test create mode 100644 core/deps/libzip/regress/extra_set_modify_l.test create mode 100644 core/deps/libzip/regress/fdopen_ok.test create mode 100644 core/deps/libzip/regress/file_comment_encmismatch.test create mode 100644 core/deps/libzip/regress/filename_duplicate.zip create mode 100644 core/deps/libzip/regress/filename_duplicate_empty.zip create mode 100644 core/deps/libzip/regress/filename_empty.zip create mode 100644 core/deps/libzip/regress/fileorder.zip create mode 100644 core/deps/libzip/regress/firstsecond-split-deflated.zip create mode 100644 core/deps/libzip/regress/firstsecond-split-stored.zip create mode 100644 core/deps/libzip/regress/firstsecond.zip create mode 100644 core/deps/libzip/regress/foo-stored.zip create mode 100644 core/deps/libzip/regress/fopen_multiple.test create mode 100644 core/deps/libzip/regress/fopen_multiple_reopen.test create mode 100644 core/deps/libzip/regress/fopen_unchanged.c create mode 100644 core/deps/libzip/regress/fopen_unchanged.test create mode 100644 core/deps/libzip/regress/fread.c create mode 100644 core/deps/libzip/regress/fread.test create mode 100644 core/deps/libzip/regress/fseek.c create mode 100644 core/deps/libzip/regress/fseek_deflated.test create mode 100644 core/deps/libzip/regress/fseek_fail.test create mode 100644 core/deps/libzip/regress/fseek_ok.test create mode 100644 core/deps/libzip/regress/fuzz_main.c create mode 100644 core/deps/libzip/regress/gap-add.zip create mode 100644 core/deps/libzip/regress/gap-delete.zip create mode 100644 core/deps/libzip/regress/gap-replace.zip create mode 100644 core/deps/libzip/regress/gap.zip create mode 100644 core/deps/libzip/regress/get_comment.test create mode 100644 core/deps/libzip/regress/hole.c create mode 100644 core/deps/libzip/regress/incons-archive-comment-longer.zip create mode 100644 core/deps/libzip/regress/incons-archive-comment-shorter.zip create mode 100644 core/deps/libzip/regress/incons-cdoffset.zip create mode 100644 core/deps/libzip/regress/incons-central-compression-method.zip create mode 100644 core/deps/libzip/regress/incons-central-compsize-larger-toolarge.zip create mode 100644 core/deps/libzip/regress/incons-central-compsize-larger.zip create mode 100644 core/deps/libzip/regress/incons-central-compsize-smaller.zip create mode 100644 core/deps/libzip/regress/incons-central-crc.zip create mode 100644 core/deps/libzip/regress/incons-central-date.zip create mode 100644 core/deps/libzip/regress/incons-central-file-comment-longer.zip create mode 100644 core/deps/libzip/regress/incons-central-file-comment-shorter.zip create mode 100644 core/deps/libzip/regress/incons-central-magic-bad.zip create mode 100644 core/deps/libzip/regress/incons-central-magic-bad2.zip create mode 100644 core/deps/libzip/regress/incons-central-size-larger.zip create mode 100644 core/deps/libzip/regress/incons-data.zip create mode 100644 core/deps/libzip/regress/incons-ef-central-size-wrong.zip create mode 100644 core/deps/libzip/regress/incons-ef-local-id-size.zip create mode 100644 core/deps/libzip/regress/incons-ef-local-id.zip create mode 100644 core/deps/libzip/regress/incons-ef-local-size.zip create mode 100644 core/deps/libzip/regress/incons-eocd-magic-bad.zip create mode 100644 core/deps/libzip/regress/incons-file-count-high.zip create mode 100644 core/deps/libzip/regress/incons-file-count-low.zip create mode 100644 core/deps/libzip/regress/incons-file-count-overflow.zip create mode 100644 core/deps/libzip/regress/incons-local-compression-method.zip create mode 100644 core/deps/libzip/regress/incons-local-compsize-larger.zip create mode 100644 core/deps/libzip/regress/incons-local-compsize-smaller.zip create mode 100644 core/deps/libzip/regress/incons-local-crc.zip create mode 100644 core/deps/libzip/regress/incons-local-filename-long.zip create mode 100644 core/deps/libzip/regress/incons-local-filename-missing.zip create mode 100644 core/deps/libzip/regress/incons-local-filename-short.zip create mode 100644 core/deps/libzip/regress/incons-local-filename.zip create mode 100644 core/deps/libzip/regress/incons-local-magic-bad.zip create mode 100644 core/deps/libzip/regress/incons-local-size-larger.zip create mode 100644 core/deps/libzip/regress/junk-at-end.zip create mode 100644 core/deps/libzip/regress/junk-at-start.zip create mode 100644 core/deps/libzip/regress/junk_at_end.test create mode 100644 core/deps/libzip/regress/junk_at_start.test create mode 100644 core/deps/libzip/regress/large-uncompressible create mode 100644 core/deps/libzip/regress/liboverride-test.c create mode 100644 core/deps/libzip/regress/liboverride.c create mode 100644 core/deps/libzip/regress/lzma-no-eos.zip create mode 100644 core/deps/libzip/regress/malloc.c create mode 100644 core/deps/libzip/regress/manyfiles-zip.zip create mode 100644 core/deps/libzip/regress/multidisk.zip create mode 100644 core/deps/libzip/regress/name_locate-cp437.test create mode 100644 core/deps/libzip/regress/name_locate-utf8.test create mode 100644 core/deps/libzip/regress/name_locate.test create mode 100644 core/deps/libzip/regress/nonrandomopen.c create mode 100644 core/deps/libzip/regress/nonrandomopentest.c create mode 100644 core/deps/libzip/regress/open_cons_extrabytes.test create mode 100644 core/deps/libzip/regress/open_empty.test create mode 100644 core/deps/libzip/regress/open_empty_2.test create mode 100644 core/deps/libzip/regress/open_extrabytes.test create mode 100644 core/deps/libzip/regress/open_file_count.test create mode 100644 core/deps/libzip/regress/open_filename_duplicate.test create mode 100644 core/deps/libzip/regress/open_filename_duplicate_consistency.test create mode 100644 core/deps/libzip/regress/open_filename_duplicate_empty.test create mode 100644 core/deps/libzip/regress/open_filename_duplicate_empty_consistency.test create mode 100644 core/deps/libzip/regress/open_filename_empty.test create mode 100644 core/deps/libzip/regress/open_incons.test create mode 100644 core/deps/libzip/regress/open_many_fail.test create mode 100644 core/deps/libzip/regress/open_many_ok.test create mode 100644 core/deps/libzip/regress/open_multidisk.test create mode 100644 core/deps/libzip/regress/open_new_but_exists.test create mode 100644 core/deps/libzip/regress/open_new_ok.test create mode 100644 core/deps/libzip/regress/open_nonarchive.test create mode 100644 core/deps/libzip/regress/open_nosuchfile.test create mode 100644 core/deps/libzip/regress/open_ok.test create mode 100644 core/deps/libzip/regress/open_too_short.test create mode 100644 core/deps/libzip/regress/open_truncate.test create mode 100644 core/deps/libzip/regress/open_zip64_3mf.test create mode 100644 core/deps/libzip/regress/open_zip64_ok.test create mode 100644 core/deps/libzip/regress/ossfuzz.sh create mode 100644 core/deps/libzip/regress/preload.test create mode 100644 core/deps/libzip/regress/progress.test create mode 100644 core/deps/libzip/regress/read_seek_read.test create mode 100644 core/deps/libzip/regress/rename_ascii.test create mode 100644 core/deps/libzip/regress/rename_cp437.test create mode 100644 core/deps/libzip/regress/rename_deleted.test create mode 100644 core/deps/libzip/regress/rename_fail.test create mode 100644 core/deps/libzip/regress/rename_ok.test create mode 100644 core/deps/libzip/regress/rename_ok.zip create mode 100644 core/deps/libzip/regress/rename_utf8.test create mode 100644 core/deps/libzip/regress/rename_utf8_encmismatch.test create mode 100644 core/deps/libzip/regress/reopen.test create mode 100644 core/deps/libzip/regress/reopen_partial.test create mode 100644 core/deps/libzip/regress/reopen_partial_rest.test delete mode 100644 core/deps/libzip/regress/runtest.in create mode 100644 core/deps/libzip/regress/set_comment_all.test create mode 100644 core/deps/libzip/regress/set_comment_localonly.test create mode 100644 core/deps/libzip/regress/set_comment_removeglobal.test create mode 100644 core/deps/libzip/regress/set_comment_revert.test create mode 100644 core/deps/libzip/regress/set_compression_bzip2_to_deflate.test create mode 100644 core/deps/libzip/regress/set_compression_deflate_to_bzip2.test create mode 100644 core/deps/libzip/regress/set_compression_deflate_to_deflate.test create mode 100644 core/deps/libzip/regress/set_compression_deflate_to_store.test create mode 100644 core/deps/libzip/regress/set_compression_lzma_no_eos_to_store.test create mode 100644 core/deps/libzip/regress/set_compression_lzma_to_store.test create mode 100644 core/deps/libzip/regress/set_compression_store_to_bzip2.test create mode 100644 core/deps/libzip/regress/set_compression_store_to_deflate.test create mode 100644 core/deps/libzip/regress/set_compression_store_to_lzma.test create mode 100644 core/deps/libzip/regress/set_compression_store_to_store.test create mode 100644 core/deps/libzip/regress/set_compression_store_to_xz.test create mode 100644 core/deps/libzip/regress/set_compression_store_to_zstd.test create mode 100644 core/deps/libzip/regress/set_compression_unknown.test create mode 100644 core/deps/libzip/regress/set_compression_xz_to_store.test create mode 100644 core/deps/libzip/regress/set_compression_zstd_to_store.test create mode 100644 core/deps/libzip/regress/set_file_dostime.test create mode 100644 core/deps/libzip/regress/set_file_mtime.test create mode 100644 core/deps/libzip/regress/set_file_mtime_pkware.test create mode 100644 core/deps/libzip/regress/short create mode 100644 core/deps/libzip/regress/source_hole.c create mode 100644 core/deps/libzip/regress/stat_index_cp437_guess.test create mode 100644 core/deps/libzip/regress/stat_index_cp437_raw.test create mode 100644 core/deps/libzip/regress/stat_index_cp437_strict.test create mode 100644 core/deps/libzip/regress/stat_index_fileorder.test create mode 100644 core/deps/libzip/regress/stat_index_streamed.test create mode 100644 core/deps/libzip/regress/stat_index_streamed_zip64.test create mode 100644 core/deps/libzip/regress/stat_index_utf8_guess.test create mode 100644 core/deps/libzip/regress/stat_index_utf8_raw.test create mode 100644 core/deps/libzip/regress/stat_index_utf8_strict.test create mode 100644 core/deps/libzip/regress/stat_index_utf8_unmarked_strict.test create mode 100644 core/deps/libzip/regress/stat_index_zip64.test create mode 100644 core/deps/libzip/regress/stored-no-eos.zip create mode 100644 core/deps/libzip/regress/streamed-zip64.zip create mode 100644 core/deps/libzip/regress/streamed.zip create mode 100644 core/deps/libzip/regress/test-cp437-comment-utf-8.zip create mode 100644 core/deps/libzip/regress/test-cp437-fc-utf-8-filename.zip create mode 100644 core/deps/libzip/regress/test-cp437-fc.zip create mode 100644 core/deps/libzip/regress/test-cp437.zip create mode 100644 core/deps/libzip/regress/test-utf8-unmarked.zip create mode 100644 core/deps/libzip/regress/test-utf8.zip create mode 100644 core/deps/libzip/regress/test.zip create mode 100644 core/deps/libzip/regress/test2.zip create mode 100644 core/deps/libzip/regress/test_open_multiple.zip create mode 100644 core/deps/libzip/regress/testbuffer.zip create mode 100644 core/deps/libzip/regress/testbuffer_reopen.zip create mode 100644 core/deps/libzip/regress/testbzip2.zip create mode 100644 core/deps/libzip/regress/testchanged.zip create mode 100644 core/deps/libzip/regress/testchangedlocal.zip create mode 100644 core/deps/libzip/regress/testcomment.zip create mode 100644 core/deps/libzip/regress/testcomment13.zip create mode 100644 core/deps/libzip/regress/testcommentremoved.zip create mode 100644 core/deps/libzip/regress/testdeflated.zip create mode 100644 core/deps/libzip/regress/testdeflated2.zip create mode 100644 core/deps/libzip/regress/testdir.zip create mode 100644 core/deps/libzip/regress/testempty.zip create mode 100644 core/deps/libzip/regress/testextrabytes.zip create mode 100644 core/deps/libzip/regress/testfile-UTF8.zip create mode 100644 core/deps/libzip/regress/testfile-cp437.zip create mode 100644 core/deps/libzip/regress/testfile-ef.zip create mode 100644 core/deps/libzip/regress/testfile-lzma.zip create mode 100644 core/deps/libzip/regress/testfile-plus-extra.zip create mode 100644 core/deps/libzip/regress/testfile-stored-dos.zip create mode 100644 core/deps/libzip/regress/testfile-torrentzip-modified.zip create mode 100644 core/deps/libzip/regress/testfile-torrentzip.zip create mode 100644 core/deps/libzip/regress/testfile-xz.zip create mode 100644 core/deps/libzip/regress/testfile-zstd.zip create mode 100644 core/deps/libzip/regress/testfile.txt create mode 100644 core/deps/libzip/regress/testfile.zip create mode 100644 core/deps/libzip/regress/testfile0.zip create mode 100644 core/deps/libzip/regress/testfile2014.zip create mode 100644 core/deps/libzip/regress/teststdin.zip create mode 100644 core/deps/libzip/regress/teststored.zip create mode 100644 core/deps/libzip/regress/truncate_empty_keep.test create mode 100644 core/deps/libzip/regress/tryopen.c create mode 100644 core/deps/libzip/regress/unchange-delete-namelocate.test create mode 100644 core/deps/libzip/regress/utf-8-standardization-input.zip create mode 100644 core/deps/libzip/regress/utf-8-standardization-output.zip create mode 100644 core/deps/libzip/regress/utf-8-standardization.test create mode 100644 core/deps/libzip/regress/want_torrentzip_stat.test create mode 100644 core/deps/libzip/regress/zip-in-archive-comment.test create mode 100644 core/deps/libzip/regress/zip-in-archive-comment.zip create mode 100644 core/deps/libzip/regress/zip64-3mf.zip create mode 100644 core/deps/libzip/regress/zip64.zip create mode 100644 core/deps/libzip/regress/zip64_creation.test create mode 100644 core/deps/libzip/regress/zip64_stored_creation.test create mode 100644 core/deps/libzip/regress/zip_read_fuzzer.cc create mode 100644 core/deps/libzip/regress/zip_read_fuzzer.dict create mode 100644 core/deps/libzip/regress/zipcmp_zip_dir.test create mode 100644 core/deps/libzip/regress/zipcmp_zip_dir.zip create mode 100644 core/deps/libzip/regress/ziptool_regress.c create mode 100644 core/deps/libzip/src/CMakeLists.txt create mode 100644 core/deps/libzip/src/diff_output.c create mode 100644 core/deps/libzip/src/diff_output.h create mode 100644 core/deps/libzip/src/getopt.c create mode 100644 core/deps/libzip/src/getopt.h create mode 100644 core/deps/libzip/src/zipcmp.c create mode 100644 core/deps/libzip/src/zipmerge.c create mode 100644 core/deps/libzip/src/ziptool.c delete mode 100644 core/deps/libzip/vstudio/readme.txt delete mode 100644 core/deps/libzip/vstudio/vsbuild.cmd delete mode 100644 core/deps/libzip/vstudio/zlib/unpack_zlib_here.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index e980792f6..95c175250 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -618,8 +618,7 @@ if(NOT LIBZIP_FOUND OR NINTENDO_SWITCH) option(BUILD_SHARED_LIBS "Build shared libraries" OFF) option(LIBZIP_DO_INSTALL "Install libzip and the related files" OFF) add_subdirectory(core/deps/libzip) - target_include_directories(${PROJECT_NAME} PRIVATE core/deps/libzip/lib) - target_link_libraries(${PROJECT_NAME} PRIVATE zip) + target_link_libraries(${PROJECT_NAME} PRIVATE libzip::zip) endif() if(WIN32) diff --git a/core/deps/libzip/.clang-format b/core/deps/libzip/.clang-format new file mode 100644 index 000000000..8bb62b8c2 --- /dev/null +++ b/core/deps/libzip/.clang-format @@ -0,0 +1,12 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +ColumnLimit: 2000 +AlwaysBreakAfterReturnType: TopLevelDefinitions +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 2 +BreakBeforeBraces: Custom +BraceWrapping: + BeforeElse: true +AlignEscapedNewlines: Left +UseTab: Never +#PPDirectiveIndentStyle: AfterHash diff --git a/core/deps/libzip/.github/ISSUE_TEMPLATE/bug-report.md b/core/deps/libzip/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 000000000..86c107807 --- /dev/null +++ b/core/deps/libzip/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,32 @@ +--- +name: Bug Report +about: Report where libzip didn't behave like you expected. +title: '' +labels: bug +assignees: '' + +--- + +**Describe the Bug** +A clear and concise description of what the bug is. + +**Expected Behavior** +A clear and concise description of what you expected to happen. + +**Observed Behavior** +A clear and concise description of what actually happened. + +**To Reproduce** +Short program or code snippet that reproduces the problem. + +**libzip Version** +Version of libzip or revision repository used. + +**Operating System** +Operating system and version, used compiler. + +**Test Files** +If applicable, attach and describe zip archives that trigger the problem. + +**Additional context** +Add any other context about the problem here. diff --git a/core/deps/libzip/.github/ISSUE_TEMPLATE/compile-error.md b/core/deps/libzip/.github/ISSUE_TEMPLATE/compile-error.md new file mode 100644 index 000000000..45c8a7a72 --- /dev/null +++ b/core/deps/libzip/.github/ISSUE_TEMPLATE/compile-error.md @@ -0,0 +1,25 @@ +--- +name: Compile Error +about: Report when libzip does not compile. +title: '' +labels: compile +assignees: '' + +--- + +**Compiler Error** +Output from the compiler, including exact and complete error message, file name and line number. + +**libzip Version** +Version of libzip or revision repository used. + +**Operating System and Compiler** +The operating system and compiler used, including version number. + +Also, any flags passed to `cmake`. + +**Autodetected Configuration** +Attach `CmakeCache.txt` from your build directory. This list everything `cmake` detected on your system. + +**Additional context** +Add any other context about the problem here. diff --git a/core/deps/libzip/.github/ISSUE_TEMPLATE/feature-request.md b/core/deps/libzip/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 000000000..c23381e02 --- /dev/null +++ b/core/deps/libzip/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,22 @@ +--- +name: Feature Request +about: Suggest an idea for this project. +title: '' +labels: enhancement +assignees: '' + +--- + +**Description** +A clear and concise description of what you want to achieve, why the current features are insufficient, and why you think it is generally useful. + +Also, have you checked whether the feature is already mentioned in TODO.md? If so, only submit a new issue if you expand on it. + +**Solution** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context about the feature request here. diff --git a/core/deps/libzip/.github/workflows/CIFuzz.yml b/core/deps/libzip/.github/workflows/CIFuzz.yml new file mode 100644 index 000000000..773e2715a --- /dev/null +++ b/core/deps/libzip/.github/workflows/CIFuzz.yml @@ -0,0 +1,25 @@ +name: CIFuzz +on: [pull_request] +permissions: + contents: read +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'libzip' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'libzip' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v1 + if: failure() + with: + name: artifacts + path: ./out/artifacts diff --git a/core/deps/libzip/.github/workflows/build.yml b/core/deps/libzip/.github/workflows/build.yml new file mode 100644 index 000000000..79030522b --- /dev/null +++ b/core/deps/libzip/.github/workflows/build.yml @@ -0,0 +1,65 @@ +name: build +on: [push] +permissions: + contents: read +jobs: + all: + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }}${{ matrix.name_extra }} + strategy: + fail-fast: false + matrix: + os: [macos-latest, ubuntu-latest, windows-latest] + cmake_extra: [""] + name_extra: [""] + include: + - os: windows-latest + cmake_extra: "-T ClangCl" + name_extra: " clang-cl" + steps: + - name: checkout + uses: actions/checkout@v3 + - name: install python and pip + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: install dependencies (Linux) + if: ${{ runner.os == 'Linux' }} + run: | + sudo apt-get install libzstd-dev + - name: install dependencies (Windows) + if: ${{ runner.os == 'Windows' }} + uses: lukka/run-vcpkg@v7 + id: runvcpkg + with: + vcpkgGitCommitId: f93ba152d55e1d243160e690bc302ffe8638358e + vcpkgTriplet: x64-windows + vcpkgArguments: zlib bzip2 liblzma zstd + - name: prepare build directory and install nihtest + run: | + cmake -E make_directory ${{runner.workspace}}/build + pip install nihtest + - name: configure (Unix) + if: ${{ runner.os != 'Windows' }} + working-directory: ${{runner.workspace}}/build + run: | + cmake ${{ matrix.cmake_extra }} ${{github.workspace}} + - name: configure (Windows) + if: ${{ runner.os == 'Windows' }} + working-directory: ${{runner.workspace}}/build + run: | + cmake ${{ matrix.cmake_extra }} -DCMAKE_TOOLCHAIN_FILE=${{env.VCPKG_ROOT}}/scripts/buildsystems/vcpkg.cmake ${{github.workspace}} + - name: build + working-directory: ${{runner.workspace}}/build + run: | + cmake --build . --config Release + - name: Archive production artifacts + uses: actions/upload-artifact@v3 + with: + name: regress-directory + path: | + ${{runner.workspace}}/build/regress + - name: test + working-directory: ${{runner.workspace}}/build + run: | + ctest --output-on-failure -v -C Release diff --git a/core/deps/libzip/.github/workflows/codeql-analysis.yml b/core/deps/libzip/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..64c659a2f --- /dev/null +++ b/core/deps/libzip/.github/workflows/codeql-analysis.yml @@ -0,0 +1,74 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +name: "CodeQL" + +on: + push: + branches: [master] + pull_request: + # The branches below must be a subset of the branches above + branches: [master] + schedule: + - cron: '0 10 * * 4' + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + # Override automatic language detection by changing the below list + # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python'] + language: ['cpp'] + # Learn more... + # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + # We must fetch at least the immediate parents so that if this is + # a pull request then we can checkout the head. + fetch-depth: 2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/core/deps/libzip/API-CHANGES.md b/core/deps/libzip/API-CHANGES.md index 24e3d8e1a..4bfb7b283 100644 --- a/core/deps/libzip/API-CHANGES.md +++ b/core/deps/libzip/API-CHANGES.md @@ -7,6 +7,18 @@ You can define `ZIP_DISABLE_DEPRECATED` before including `` to hide prototypes for deprecated functions, to find out about functions that might be removed at some point. +## Changed in libzip-1.10.0 + +### deprecated `zip_source_zip` and `zip_source_zip_create` + +These functions were replaced with `zip_source_zip_file` and `zip_source_zip_file_create`. The implicit handling of the flag `ZIP_FL_COMPRESSED` was removed, the flag can now be specified explicitly. + +If you want to get the compressed data for the whole file, use + +```C +zip_source_zip_file(za, source_archive, source_index, ZIP_FL_COMPRESSED, 0, -1, NULL) +``` + ## Changed in libzip-1.0 ### new type `zip_error_t` diff --git a/core/deps/libzip/CMakeLists.txt b/core/deps/libzip/CMakeLists.txt index afcc6fc31..b2e77241a 100644 --- a/core/deps/libzip/CMakeLists.txt +++ b/core/deps/libzip/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0.2) +cmake_minimum_required(VERSION 3.5.0) list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) if (${CMAKE_VERSION} VERSION_LESS "3.17.0") @@ -6,7 +6,7 @@ if (${CMAKE_VERSION} VERSION_LESS "3.17.0") endif() project(libzip - VERSION 1.7.3.1 + VERSION 1.10.1 LANGUAGES C) option(ENABLE_COMMONCRYPTO "Enable use of CommonCrypto" ON) @@ -19,6 +19,8 @@ option(ENABLE_BZIP2 "Enable use of BZip2" ON) option(ENABLE_LZMA "Enable use of LZMA" ON) option(ENABLE_ZSTD "Enable use of Zstandard" ON) +option(ENABLE_FDOPEN "Enable zip_fdopen, which is not allowed in Microsoft CRT secure libraries" ON) + option(BUILD_TOOLS "Build tools in the src directory (zipcmp, zipmerge, ziptool)" ON) option(BUILD_REGRESS "Build regression tests" ON) option(BUILD_EXAMPLES "Build examples" ON) @@ -87,14 +89,19 @@ endif() # Checks +# Request ISO C secure library functions (memcpy_s &c) +list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__STDC_WANT_LIB_EXT1__=1) + check_function_exists(_close HAVE__CLOSE) check_function_exists(_dup HAVE__DUP) check_function_exists(_fdopen HAVE__FDOPEN) check_function_exists(_fileno HAVE__FILENO) check_function_exists(_setmode HAVE__SETMODE) -check_function_exists(_snprintf HAVE__SNPRINTF) +check_symbol_exists(_snprintf stdio.h HAVE__SNPRINTF) +check_symbol_exists(_snprintf_s stdio.h HAVE__SNPRINTF_S) +check_symbol_exists(_snwprintf_s stdio.h HAVE__SNWPRINTF_S) check_function_exists(_strdup HAVE__STRDUP) -check_function_exists(_stricmp HAVE__STRICMP) +check_symbol_exists(_stricmp string.h HAVE__STRICMP) check_function_exists(_strtoi64 HAVE__STRTOI64) check_function_exists(_strtoui64 HAVE__STRTOUI64) check_function_exists(_unlink HAVE__UNLINK) @@ -102,15 +109,24 @@ check_function_exists(arc4random HAVE_ARC4RANDOM) check_function_exists(clonefile HAVE_CLONEFILE) check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO) check_function_exists(explicit_memset HAVE_EXPLICIT_MEMSET) +check_function_exists(fchmod HAVE_FCHMOD) check_function_exists(fileno HAVE_FILENO) check_function_exists(fseeko HAVE_FSEEKO) check_function_exists(ftello HAVE_FTELLO) check_function_exists(getprogname HAVE_GETPROGNAME) -check_function_exists(localtime_r HAVE_LOCALTIME_R) +check_symbol_exists(localtime_r time.h HAVE_LOCALTIME_R) +check_symbol_exists(localtime_s time.h HAVE_LOCALTIME_S) +check_function_exists(memcpy_s HAVE_MEMCPY_S) +check_function_exists(random HAVE_RANDOM) check_function_exists(setmode HAVE_SETMODE) -check_function_exists(strcasecmp HAVE_STRCASECMP) +check_symbol_exists(snprintf stdio.h HAVE_SNPRINTF) +check_symbol_exists(snprintf_s stdio.h HAVE_SNPRINTF_S) +check_symbol_exists(strcasecmp strings.h HAVE_STRCASECMP) check_function_exists(strdup HAVE_STRDUP) +check_function_exists(strerror_s HAVE_STRERROR_S) +check_function_exists(strerrorlen_s HAVE_STRERRORLEN_S) check_function_exists(stricmp HAVE_STRICMP) +check_function_exists(strncpy_s HAVE_STRNCPY_S) check_function_exists(strtoll HAVE_STRTOLL) check_function_exists(strtoull HAVE_STRTOULL) @@ -175,6 +191,34 @@ int main(int argc, char *argv[]) { }" HAVE_NULLABLE) test_big_endian(WORDS_BIGENDIAN) find_package(ZLIB 1.1.2 REQUIRED) +# so developers on systems where zlib is named differently (Windows, sometimes) +# can override the name used in the pkg-config file +if (NOT ZLIB_LINK_LIBRARY_NAME) + set(ZLIB_LINK_LIBRARY_NAME "z") + + # Get the correct name in common cases + list(LENGTH ZLIB_LIBRARIES N_ZLIB_LIBRARIES) + if(N_ZLIB_LIBRARIES EQUAL 1) + set(ZLIB_FILENAME ${ZLIB_LIBRARIES}) + elseif(N_ZLIB_LIBRARIES EQUAL 4) + # ZLIB_LIBRARIES might have the target_link_library() format like + # "optimized;path/to/zlib.lib;debug;path/to/zlibd.lib". Use the 'optimized' + # case unless we know we are in a Debug build. + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + list(FIND ZLIB_LIBRARIES "debug" ZLIB_LIBRARIES_INDEX_OF_CONFIG) + else() + list(FIND ZLIB_LIBRARIES "optimized" ZLIB_LIBRARIES_INDEX_OF_CONFIG) + endif() + if(ZLIB_LIBRARIES_INDEX_OF_CONFIG GREATER_EQUAL 0) + math(EXPR ZLIB_FILENAME_INDEX "${ZLIB_LIBRARIES_INDEX_OF_CONFIG}+1") + list(GET ZLIB_LIBRARIES ${ZLIB_FILENAME_INDEX} ZLIB_FILENAME) + endif() + endif() + if(ZLIB_FILENAME) + get_filename_component(ZLIB_FILENAME ${ZLIB_FILENAME} NAME_WE) + string(REGEX REPLACE "^lib" "" ZLIB_LINK_LIBRARY_NAME ${ZLIB_FILENAME}) + endif() +endif(NOT ZLIB_LINK_LIBRARY_NAME) if(ENABLE_BZIP2) find_package(BZip2) @@ -195,12 +239,17 @@ if(ENABLE_LZMA) endif(ENABLE_LZMA) if(ENABLE_ZSTD) - find_package(Zstd) - if(Zstd_FOUND) + find_package(zstd 1.3.6) + if(zstd_FOUND) set(HAVE_LIBZSTD 1) + if(TARGET zstd::libzstd_shared) + set(zstd_TARGET zstd::libzstd_shared) + else() + set(zstd_TARGET zstd::libzstd_static) + endif() else() message(WARNING "-- zstd library not found; zstandard support disabled") - endif(Zstd_FOUND) + endif(zstd_FOUND) endif(ENABLE_ZSTD) if (COMMONCRYPTO_FOUND) @@ -209,18 +258,18 @@ if (COMMONCRYPTO_FOUND) elseif (WINDOWS_CRYPTO_FOUND) set(HAVE_CRYPTO 1) set(HAVE_WINDOWS_CRYPTO 1) -elseif (GNUTLS_FOUND AND NETTLE_FOUND) - set(HAVE_CRYPTO 1) - set(HAVE_GNUTLS 1) elseif (OPENSSL_FOUND) set(HAVE_CRYPTO 1) set(HAVE_OPENSSL 1) +elseif (GNUTLS_FOUND AND NETTLE_FOUND) + set(HAVE_CRYPTO 1) + set(HAVE_GNUTLS 1) elseif (MBEDTLS_FOUND) set(HAVE_CRYPTO 1) set(HAVE_MBEDTLS 1) endif() -if (NOT HAVE_CRYPTO) +if ((ENABLE_COMMONCRYPTO OR ENABLE_GNUTLS OR ENABLE_MBEDTLS OR ENABLE_OPENSSL OR ENABLE_WINDOWS_CRYPTO) AND NOT HAVE_CRYPTO) message(WARNING "-- neither Common Crypto, GnuTLS, mbed TLS, OpenSSL, nor Windows Cryptography found; AES support disabled") endif() @@ -263,10 +312,10 @@ else(BUILD_TOOLS) endif(BUILD_REGRESS) endif() -include(FindPerl) +find_program(NIHTEST nihtest) -if(NOT PERL_FOUND) - message(WARNING "-- perl not found, regression testing disabled") +if(BUILD_REGRESS AND NOT NIHTEST) + message(WARNING "-- nihtest not found, regression testing disabled") set(BUILD_REGRESS OFF) endif() @@ -280,11 +329,12 @@ endif() # pkgconfig file -set(prefix ${CMAKE_INSTALL_PREFIX}) -set(exec_prefix \${prefix}) -SET(bindir ${CMAKE_INSTALL_FULL_BINDIR}) -SET(libdir ${CMAKE_INSTALL_FULL_LIBDIR}) -SET(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR}) +file(RELATIVE_PATH pc_relative_bindir ${CMAKE_INSTALL_PREFIX} ${CMAKE_INSTALL_FULL_BINDIR}) +set(bindir "\${prefix}/${pc_relative_bindir}") +file(RELATIVE_PATH pc_relative_libdir ${CMAKE_INSTALL_PREFIX} ${CMAKE_INSTALL_FULL_LIBDIR}) +set(libdir "\${prefix}/${pc_relative_libdir}") +file(RELATIVE_PATH pc_relative_includedir ${CMAKE_INSTALL_PREFIX} ${CMAKE_INSTALL_FULL_INCLUDEDIR}) +set(includedir "\${prefix}/${pc_relative_includedir}") if(CMAKE_SYSTEM_NAME MATCHES BSD) set(PKG_CONFIG_RPATH "-Wl,-R\${libdir}") endif(CMAKE_SYSTEM_NAME MATCHES BSD) @@ -296,10 +346,14 @@ foreach(LIB ${LIBS_PRIVATE}) endif() set(LIBS "${LIBS} -l${LIB}") endforeach() +STRING(CONCAT zlib_link_name "-l" ${ZLIB_LINK_LIBRARY_NAME}) string(REGEX REPLACE "-lBZip2::BZip2" "-lbz2" LIBS ${LIBS}) string(REGEX REPLACE "-lLibLZMA::LibLZMA" "-llzma" LIBS ${LIBS}) +if(ENABLE_ZSTD) + string(REGEX REPLACE "-l${zstd_TARGET}" "-lzstd" LIBS ${LIBS}) +endif() string(REGEX REPLACE "-lOpenSSL::Crypto" "-lssl -lcrypto" LIBS ${LIBS}) -string(REGEX REPLACE "-lZLIB::ZLIB" "-lz" LIBS ${LIBS}) +string(REGEX REPLACE "-lZLIB::ZLIB" ${zlib_link_name} LIBS ${LIBS}) string(REGEX REPLACE "-lGnuTLS::GnuTLS" "-lgnutls" LIBS ${LIBS}) string(REGEX REPLACE "-lNettle::Nettle" "-lnettle" LIBS ${LIBS}) configure_file(libzip.pc.in libzip.pc @ONLY) @@ -409,17 +463,6 @@ set(srcdir ${CMAKE_CURRENT_SOURCE_DIR}/regress) set(abs_srcdir ${CMAKE_CURRENT_SOURCE_DIR}/regress) set(top_builddir ${PROJECT_BINARY_DIR}) # used to find config.h -configure_file(regress/nihtest.conf.in ${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/nihtest.conf @ONLY) -file(COPY ${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/nihtest.conf - DESTINATION ${PROJECT_BINARY_DIR}/regress - FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) - -configure_file(regress/runtest.in ${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/runtest @ONLY) -file(COPY ${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/runtest - DESTINATION ${PROJECT_BINARY_DIR}/regress - FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE - ) - # create package config file include(CMakePackageConfigHelpers) write_basic_package_version_file("${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" @@ -428,6 +471,15 @@ write_basic_package_version_file("${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config-v configure_package_config_file("${PROJECT_NAME}-config.cmake.in" "${PROJECT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/libzip) +# Install Find* modules, they are required by libzip-config.cmake to resolve dependencies +install(FILES + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindNettle.cmake + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Findzstd.cmake + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/FindMbedTLS.cmake + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/libzip/modules + ) + if(LIBZIP_DO_INSTALL) # Add targets to the build-tree export set export(TARGETS zip @@ -442,4 +494,3 @@ if(LIBZIP_DO_INSTALL) DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} ) endif() - diff --git a/core/deps/libzip/INSTALL.md b/core/deps/libzip/INSTALL.md index 512768cf5..83f1d97db 100644 --- a/core/deps/libzip/INSTALL.md +++ b/core/deps/libzip/INSTALL.md @@ -1,7 +1,5 @@ libzip uses [cmake](https://cmake.org) to build. -For running the tests, you need to have [perl](https://www.perl.org). - You'll need [zlib](http://www.zlib.net/) (at least version 1.1.2). It comes with most operating systems. @@ -18,15 +16,19 @@ For AES (encryption) support, you need one of these cryptographic libraries, listed in order of preference: - Apple's CommonCrypto (available on macOS and iOS) +- Microsoft Windows Cryptography Framework +- [OpenSSL](https://www.openssl.org/) >= 1.0. - [GnuTLS](https://www.gnutls.org/) and [Nettle](https://www.lysator.liu.se/~nisse/nettle/) (at least nettle 3.0) - [mbed TLS](https://tls.mbed.org/) -- [OpenSSL](https://www.openssl.org/) >= 1.0. -- Microsoft Windows Cryptography Framework If you don't want a library even if it is installed, you can pass `-DENABLE_=OFF` to cmake, where `` is one of `COMMONCRYPTO`, `GNUTLS`, `MBEDTLS`, or `OPENSSL`. +For running the tests, you need to have +[Python](https://www.python.org/) and +[nihtest](https://pypi.org/project/nihtest/) installed. + The basic usage is ```sh mkdir build @@ -48,7 +50,7 @@ Some useful parameters you can pass to `cmake` with `-Dparameter=value`: - `LIBZIP_DO_INSTALL`: If you include libzip as a subproject, link it statically and do not want to let it install its files, set this variable to `OFF`. Defaults to `ON`. - + If you want to compile with custom `CFLAGS`, set them in the environment before running `cmake`: ```sh @@ -66,4 +68,4 @@ will break in `zipcmp`. You can get verbose build output with by passing `VERBOSE=1` to `make`. -You can also check the [cmake FAQ](https://cmake.org/Wiki/CMake_FAQ). +You can also check the [cmake FAQ](https://gitlab.kitware.com/cmake/community/-/wikis/FAQ). diff --git a/core/deps/libzip/LICENSE b/core/deps/libzip/LICENSE index 573d5d94d..fa7060961 100644 --- a/core/deps/libzip/LICENSE +++ b/core/deps/libzip/LICENSE @@ -1,6 +1,6 @@ Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner -The authors can be contacted at +The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/NEWS.md b/core/deps/libzip/NEWS.md index ea1dd9ff4..e117422d8 100644 --- a/core/deps/libzip/NEWS.md +++ b/core/deps/libzip/NEWS.md @@ -1,32 +1,81 @@ -1.8.0 [2020-xx-xx] -================== +# 1.10.1 [2023-08-23] + +* Add `ZIP_LENGTH_TO_END` and `ZIP_LENGTH_UNCHECKED`. Unless `ZIP_LENGTH_UNCHECKED` is used as `length`, it is an error for a file to shrink between the time when the source is created and when its data is read. +* Fix test on Windows. + +# 1.10.0 [2023-06-23] + +* Make support for layered sources public. +* Add `zip_source_zip_file` and `zip_source_zip_file_create`, deprecate `zip_source_zip` and `zip_source_zip_create`. +* Allow reading changed file data. +* Fix handling of files of size 4294967295. +* `zipmerge`: copy extra fields. +* `zipmerge`: add option to keep files uncompressed. +* Switch test framework to use nihtest instead of Perl. +* Fix reading/writing compressed data with buffers > 4GiB. +* Restore support for torrentzip. +* Add warnings when using deprecated functions. +* Allow keeping files for empty archives. +* Support mbedTLS>=3.3.0. +* Support OpenSSL 3. +* Use ISO C secure library functions, if available. + + +# 1.9.2 [2022-06-28] + +* Fix version number in header file. + + +# 1.9.1 [2022-06-28] + +* Fix `zip_file_is_seekable()`. + + +# 1.9.0 [2022-06-13] + +* Add `zip_file_is_seekable()`. +* Improve compatibility with WinAES. +* Fix encoding handling in `zip_name_locate()`. +* Add option to `zipcmp` to output summary of changes. +* Various bug fixes and documentation improvements. + + +# 1.8.0 [2021-06-18] * Add support for zstd (Zstandard) compression. * Add support for lzma (ID 14) compression. +* Add `zip_source_window_create()`. +* Add `zip_source_zip_create()` variant to `zip_source_zip()`. +* Allow method specific `comp_flags` in `zip_set_file_compression()`. +* Allow `zip_source_tell()` on sources that don't support seeking and `zip_ftell()` on compressed data. +* Provide more details for consistency check errors. +* Improve output of `zipcmp`. +* In `zipcmp`, don’t ignore empty directories when comparing directory listing. +* Treat empty string as no password given in `zip_file_set_encryption()`, `zip_fopen_encrypted()`, and `zip_set_default_password()`. -1.7.3 [2020-07-15] -================== + +# 1.7.3 [2020-07-15] * Support cmake < 3.17 again. * Fix pkgconfig file (regression in 1.7.2). -1.7.2 [2020-07-11] -================== + +# 1.7.2 [2020-07-11] * Fixes for the CMake `find_project()` files. * libzip moved to the CMake `libzip::` `NAMESPACE`. * CMake usage best practice cleanups. -1.7.1 [2020-06-13] -================== + +# 1.7.1 [2020-06-13] * Restore `LIBZIP_VERSION_{MAJOR,MINOR,MICRO}` symbols. * Fixes warnings reported by PVS-Studio. * Add `LIBZIP_DO_INSTALL` build setting to make it easier to use libzip as subproject. -1.7.0 [2020-06-05] -================== + +# 1.7.0 [2020-06-05] * Add support for encrypting using traditional PKWare encryption. * Add `zip_compression_method_supported()`. @@ -35,13 +84,13 @@ * Refactor stdio file backend. * Add CMake find_project() support. -1.6.1 [2020-02-03] -================== + +# 1.6.1 [2020-02-03] * Bugfix for double-free in `zipcmp(1)` during cleanup. -1.6.0 [2020-01-24] -================== + +# 1.6.0 [2020-01-24] * Avoid using `umask()` since it's not thread-safe. * Set close-on-exec flag when opening files. @@ -50,8 +99,8 @@ * Add support for cancelling while closing zip archives. * Add support for setting the time in the on-disk format. -1.5.2 [2019-03-12] -================== + +# 1.5.2 [2019-03-12] * Fix bug in AES encryption affecting certain file sizes * Keep file permissions when modifying zip archives @@ -59,8 +108,8 @@ * Support mbed TLS as crypto backend. * Add nullability annotations. -1.5.1 [2018-04-11] -================== + +# 1.5.1 [2018-04-11] * Choose format of installed documentation based on available tools. * Fix visibility of symbols. @@ -70,16 +119,16 @@ * Fix build with LibreSSL. * Various bugfixes. -1.5.0 [2018-03-11] -================== + +# 1.5.0 [2018-03-11] * Use standard cryptographic library instead of custom AES implementation. This also simplifies the license. * Use `clang-format` to format the source code. * More Windows improvements. -1.4.0 [2017-12-29] -================== + +# 1.4.0 [2017-12-29] * Improve build with cmake * Retire autoconf/automake build system @@ -88,20 +137,20 @@ Supported for buffer sources and on Apple File System. * Add support for Microsoft Universal Windows Platform. -1.3.2 [2017-11-20] -================== + +# 1.3.2 [2017-11-20] * Fix bug introduced in last: zip_t was erroneously freed if zip_close() failed. -1.3.1 [2017-11-19] -================== + +# 1.3.1 [2017-11-19] * Install zipconf.h into ${PREFIX}/include * Add zip_libzip_version() * Fix AES tests on Linux -1.3.0 [2017-09-02] -================== + +# 1.3.0 [2017-09-02] * Support bzip2 compressed zip archives * Improve file progress callback code @@ -109,8 +158,8 @@ * CVE-2017-12858: Fix double free() * CVE-2017-14107: Improve EOCD64 parsing -1.2.0 [2017-02-19] -================== + +# 1.2.0 [2017-02-19] * Support for AES encryption (Winzip version), both encryption and decryption @@ -120,24 +169,24 @@ * Add zip_ftell() for telling position in uncompressed data * Add zip_register_progress_callback() for UI updates during zip_close() -1.1.3 [2016-05-28] -================== + +# 1.1.3 [2016-05-28] * Fix build on Windows when using autoconf -1.1.2 [2016-02-19] -================== + +# 1.1.2 [2016-02-19] * Improve support for 3MF files -1.1.1 [2016-02-07] -================== + +# 1.1.1 [2016-02-07] * Build fixes for Linux * Fix some warnings reported by PVS-Studio -1.1 [2016-01-26] -================ + +# 1.1 [2016-01-26] * ziptool(1): command line tool to modify zip archives * Speedups for archives with many entries @@ -148,13 +197,13 @@ * Portability fixes * Documentation improvements -1.0.1 [2015-05-04] -================== + +# 1.0.1 [2015-05-04] * Build fixes for Windows -1.0 [2015-05-03] -================ + +# 1.0 [2015-05-03] * Implemented an I/O abstraction layer * Added support for native Windows API for files @@ -165,22 +214,22 @@ * CVE-2015-2331 was fixed * Addressed all Coverity CIDs -0.11.2 [2013-12-19] -=================== + +# 0.11.2 [2013-12-19] * Support querying/setting operating system and external attributes * For newly added files, set operating system to UNIX, permissions to 0666 (0777 for directories) * Fix bug when writing zip archives containing files bigger than 4GB -0.11.1 [2013-04-27] -=================== + +# 0.11.1 [2013-04-27] * Fix bugs in zip_set_file_compression() * Include Xcode build infrastructure -0.11 [2013-03-23] -================= + +# 0.11 [2013-03-23] * Added Zip64 support (large file support) * Added UTF-8 support for file names, file comments, and archive comments @@ -194,14 +243,14 @@ * More changes for Windows support * Additional test cases -0.10.1 [2012-03-20] -=================== + +# 0.10.1 [2012-03-20] * Fixed CVE-2012-1162 * Fixed CVE-2012-1163 -0.10 [2010-03-18] -================= + +# 0.10 [2010-03-18] * Added zip_get_num_entries(), deprecated zip_get_num_files() * Better windows support @@ -213,27 +262,27 @@ * Fixed CVE-2011-0421 (no security implications though) * More documentation -0.9.3 [2010-02-01] -================== + +# 0.9.3 [2010-02-01] * Include m4/ directory in distribution; some packagers need it -0.9.2 [2010-01-31] -================== + +# 0.9.2 [2010-01-31] * Avoid passing uninitialized data to deflate() * Fix memory leak when closing zip archives -0.9.1 [2010-01-24] -================== + +# 0.9.1 [2010-01-24] * Fix infinite loop on reading some broken files * Optimization in time conversion (don't call localtime()) * Clear data descriptor flag in central directory, fixing Open Office files * Allow more than 64k entries -0.9 [2008-07-25] -================== + +# 0.9 [2008-07-25] * on Windows, explicitly set dllimport/dllexport * remove erroneous references to GPL @@ -242,8 +291,8 @@ * zip_source_zip: add flag to force recompression * zip_sorce_file: only keep file open while reading from it -0.8 [2007-06-06] -================== + +# 0.8 [2007-06-06] * fix for zip archives larger than 2GiB * fix zip_error_strerror to include libzip error string @@ -251,13 +300,13 @@ * new functions: zip_add_dir, zip_error_clear, zip_file_error_clear * add basic support for building with CMake (incomplete) -0.7.1 [2006-05-18] -================== + +# 0.7.1 [2006-05-18] * bugfix for zip_close -0.7 [2006-05-06] -================ + +# 0.7 [2006-05-06] * struct zip_stat increased for future encryption support * zip_add return value changed (now returns new index of added file) @@ -266,13 +315,13 @@ New functions: zip_get_archive_comment, zip_get_file_comment, zip_set_archive_comment, zip_set_file_comment, zip_unchange_archive -0.6.1 [2005-07-14] -================== + +# 0.6.1 [2005-07-14] * various bug fixes -0.6 [2005-06-09] -================ + +# 0.6 [2005-06-09] * first standalone release * changed license to three-clause BSD diff --git a/core/deps/libzip/README.md b/core/deps/libzip/README.md index 9262c3def..2ca60e1d7 100644 --- a/core/deps/libzip/README.md +++ b/core/deps/libzip/README.md @@ -3,7 +3,7 @@ zip and zip64 archives. Files can be added from data buffers, files, or compressed data copied directly from other zip archives. Changes made without closing the archive can be reverted. Decryption and encryption of Winzip AES and legacy PKware encrypted files is -supported. The API is documented by man pages. +supported. libzip is fully documented via man pages. HTML versions of the man pages are on [libzip.org](https://libzip.org/documentation/) and @@ -13,13 +13,13 @@ lists all others. Example source code is in the [examples](examples) and [src](src) subdirectories. +See the [INSTALL.md](INSTALL.md) file for installation instructions and +dependencies. + If you have developed an application using libzip, you can find out about API changes and how to adapt your code for them in the included file [API-CHANGES.md](API-CHANGES.md). -See the [INSTALL.md](INSTALL.md) file for installation instructions and -dependencies. - If you make a binary distribution, please include a pointer to the distribution site: > https://libzip.org/ @@ -27,13 +27,8 @@ distribution site: The latest version can always be found there. The official repository is at [github](https://github.com/nih-at/libzip/). -There is a mailing list for developers using libzip. You can -subscribe to it by sending a mail with the subject "subscribe -libzip-discuss" to minimalist at nih.at. List mail should be sent -to libzip-discuss at nih.at. Use this for bug reports or questions. +If you want to reach the authors in private, use . -If you want to reach the authors in private, use . - -[![Travis Build Status](https://api.travis-ci.org/nih-at/libzip.svg?branch=master)](https://travis-ci.org/nih-at/libzip) +[![Github Actions Build Status](https://github.com/nih-at/libzip/workflows/build/badge.svg)](https://github.com/nih-at/libzip/actions?query=workflow%3Abuild) [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/f1bqqt9djvf22f5g?svg=true)](https://ci.appveyor.com/project/nih-at/libzip) [![Coverity Status](https://scan.coverity.com/projects/127/badge.svg)](https://scan.coverity.com/projects/libzip) diff --git a/core/deps/libzip/SECURITY.md b/core/deps/libzip/SECURITY.md new file mode 100644 index 000000000..67c84eb1d --- /dev/null +++ b/core/deps/libzip/SECURITY.md @@ -0,0 +1,13 @@ +# Security Policy + +## Supported Versions + +We are not maintaining multiple branches, so all fixes will be committed to head and included in the next release. + +We take great care to maintain backwards compatibility, so we expect our users to use the latest version. + +## Reporting a Vulnerability + +You can reach us per email at info@libzip.org. + +For less sensitive reports, you can also open an issue or pull request on GitHub. diff --git a/core/deps/libzip/THANKS b/core/deps/libzip/THANKS index b7e4497f6..d36ecc9e5 100644 --- a/core/deps/libzip/THANKS +++ b/core/deps/libzip/THANKS @@ -3,10 +3,13 @@ and some other general information gathered from their sources. Thanks to these people for suggestions, testing, and bug reports: +ag2s20150909 Agostino Sarubbo +Alberto Spin Alexander Galanin Alexandr Shadchin Alexey Bykov +Andreas Deininger Andreas Falkenhahn Andrew Brampton Andrew Molyneux @@ -19,36 +22,50 @@ Beuc Boaz Stolk Bogdan Brian 'geeknik' Carpenter +BruceFan Carl Mastrangelo Cédric Tabin celan69 +chaoticgd Charlie Li Chris Nehren Christoph Cullmann Christoph M. Becker Coverity +cryi +ctenter-scs Dane Springmeyer +Daniel Russel Ларионов Даниил David Demelier Dean Ellis Declan Moran Del Merritt +Devin Davila Dmytro Rybachenko +Dylan T. +Eelco Dolstra Elvis Angelaccio Erwin Haid Eun-cheol Joo Fabrice Fontaine +Filip Niksic Florian Delizy Force Charlie François Simon Frederik Ramm +Gabriela Gutierrez +Gerard ODonnell +Giovanni gk7huki Hanno Böck HeeMyung Heiko Becker Heiko Hund +hongjunwang Ilya Voronin Info-ZIP group +Ivan Kolesnikov Jan Weiß Jay Freeman (saurik) jloqfjgk@github @@ -56,24 +73,35 @@ Joachim Reichel João Custódio Joel Ebrahimi Jono Spiro +Julien Matthey Julien Schueller +Justin Cohen kensington Keith Jones Khaled Mardam-Bey Kohei Yoshida +Krzesimir Nowak Leith Bade Lubomir I. Ivanov +Lucas Bustamante +Ludovic LANGE +M. Reiningħaus Maël Nison +Manuel Massing +Marcin Kowalczyk +Mark A. Tsuchida Martin Buchholz Martin Herkt Martin Szulecki Michael Balzer Michael Beck +Michael Heimpold Michał Janiszewski Michal Vyskocil Mikhail Gusarov . Miklos Vajna Morris Hafner +Muhammad Arslan Kabeer Oliver Kaiser Oliver Kuckertz OSS-Fuzz Team @@ -84,34 +112,51 @@ Paul Harris Paul Sheppard Pavel Raiskup Pierre Joye +Pierre Wendling Pierre-Louis Cabelguen +PW Hu +Rafał Mikrut +ralfjunker Randy Remi Collet +rezso Richard Schütz Rick Carback Rikard Falkeborn Robert Norris Roberto Tirabassi +robhz786 Roland Ortloff Rosen Penev +Rudi Heitbaum Ryan Burns +Sam Sappenfield +scribam Sebastian Kemper Sebastian Schmitt Sergei Ozerov +shenlebantongying +Shimi Simon Talbot +SpaceIm Stephen Bryant Tabata Shintaro Tarmo Pikaro Taylor C. Richberger TC +Thomas Debesse Tim Lunn Timo Warns +Timofey Tom Callaway Tomas Hoger Tomáš Malý Torsten Paul Transporter Vassili Courzakis +Vinpasso +Vitaly Murashev William Lee +William Ouwehand Wojciech Michalski Wolfgang Glunz diff --git a/core/deps/libzip/TODO.md b/core/deps/libzip/TODO.md index bd81107ac..3eeba6156 100644 --- a/core/deps/libzip/TODO.md +++ b/core/deps/libzip/TODO.md @@ -1,3 +1,14 @@ +### Torrentzip + +- Handle data sources with unknown uncompressed size. +- Handle when uncompressed size < 4GB but compressed size > 4GB. + +## Other + +- split `zip_source_t` in main part and reference so we can keep track which reference called open and we can invalidate references if the underlying source gets invalidated (e. g. by `zip_close`). +- Support extended timestamp extra field (0x5455): mtime overrides dos mtime from dirent, function to get/set all three. +- Check UTF-8 code against https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + ## Prefixes For example for adding extractors for self-extracting zip archives. @@ -13,6 +24,8 @@ const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp) ## API Issues +* Add `zip_file_use_password` to set per-file password to use if libzip needs to decrypt the file (e.g. when changing encryption or compression method). + * `zip_get_archive_comment` has `int *lenp` argument. Cleaner would be `zip_uint32_t *`. rename and fix. which other functions for naming consistency? * rename remaining `zip_XXX_{file,archive}_*` to `zip_{file,archive}_XXX_*`? @@ -21,7 +34,6 @@ const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp) ## Features -* add seek support for AES-encrypted files * consistently use `_zip_crypto_clear()` for passwords * support setting extra fields from `zip_source` * introduce layers of extra fields: @@ -75,6 +87,7 @@ const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp) ## Documentation +* document valid file paths * document: `zip_source_write()`: length can't be > `ZIP_INT64_MAX` * document: `ZIP_SOURCE_CLOSE` implementation can't return error * keep error codes in man pages in sync @@ -82,11 +95,12 @@ const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp) ## Infrastructure +* add coverage reports, e.g. using gcovr or https://github.com/eddyxu/cpp-coveralls (coveralls.io) * review guidelines/community standards - [Linux Foundation Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/) - [Readme Maturity Level](https://github.com/LappleApple/feedmereadmes/blob/master/README-maturity-model.md) - [Github Community Profile](https://github.com/nih-at/libzip/community) -* test different crypto backends with TravisCI. +* test different crypto backends with GitHub actions. * improve man page formatting of tagged lists on webpage (`
`) * rewrite `make_zip_errors.sh` in cmake * script to check if all exported symbols are marked with `ZIP_EXTERN`, add to `make distcheck` @@ -97,6 +111,8 @@ const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp) ## Test Case Issues +* add test cases for all `ZIP_INCONS` detail errors +* `incons-local-filename-short.zzip` doesn't test short filename, since extra fields fail to parse. * test error cases with special source - tell it which command should fail - use it both as source for `zip_add` and `zip_open_from_source` @@ -113,14 +129,14 @@ const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp) - state of source (opened, EOF reached, ...) * test for zipcmp reading directory (requires fts) * add test case for clone with files > 4k -* consider testing for malloc/realloc failures +* consider testing for `malloc`/`realloc` failures * Winzip AES support * test cases decryption: <=20, >20, stat for both * test cases encryption: no password, default password, file-specific password, 128/192/256, <=20, >20 * support testing on macOS * add test cases for lots of files (including too many) * add test cases for holes (between files, between files and cdir, between cdir and eocd, + zip64 where appropriate) -* test seek in `zip_source_crc()` +* test seek in `zip_source_crc_create()` * test cases for `set_extra*`, `delete_extra*`, `*extra_field*` * test cases for in memory archives * add @@ -150,7 +166,7 @@ const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp) * close * zipcmp copy expected * remove copy -* (`error_get) +* (`error_get`) * (`error_get_sys_type`) * (`error_to_str`) * (`extra_fields`) @@ -170,3 +186,4 @@ const zip_uint8_t *zip_get_archive_prefix(struct zip *za, zip_uint64_t *lengthp) * I/O abstraction layer * `zip_open_from_source` * read two zip entries interleaved +* test `zip_file_is_seekable` (via `ziptool`?) diff --git a/core/deps/libzip/android/do.sh b/core/deps/libzip/android/do.sh index c4641c9f2..0c72242af 100644 --- a/core/deps/libzip/android/do.sh +++ b/core/deps/libzip/android/do.sh @@ -20,7 +20,7 @@ build_it() want_shared=$1 cmake -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake \ - -DCMAKE_INSTALL_PREFIX:PATH=../../${INSTALL_DIR}/${ANDROID_TARGET_PLATFORM} \ + -DCMAKE_INSTALL_PREFIX:PATH=$(pwd)/../../${INSTALL_DIR}/${ANDROID_TARGET_PLATFORM} \ -DANDROID_ABI=${ANDROID_TARGET_PLATFORM} \ -DENABLE_OPENSSL:BOOL=OFF \ -DENABLE_COMMONCRYPTO:BOOL=OFF \ diff --git a/core/deps/libzip/android/readme.txt b/core/deps/libzip/android/readme.txt index aa69fbd45..dadc4b479 100644 --- a/core/deps/libzip/android/readme.txt +++ b/core/deps/libzip/android/readme.txt @@ -8,3 +8,6 @@ Prerequisites for the development machine - see docker/Dockerfile You can either set you host machine up with these prerequisites or simply use docker (in which case you need not install anything on your host machine except docker itself). See "Usage" in docker/Dockerfile for detailed instructions. + + +Please note: The libzip development team does not use Android, so this script is provided as is, as we cannot properly maintain it. We will, however, gladly accept fixes and try to work with users to resolve any issues they may have. diff --git a/core/deps/libzip/appveyor.yml b/core/deps/libzip/appveyor.yml new file mode 100644 index 000000000..609f1a60a --- /dev/null +++ b/core/deps/libzip/appveyor.yml @@ -0,0 +1,90 @@ +os: +- Visual Studio 2019 + +environment: + PATH: C:\Python311-x64\Scripts;C:\Python311-arm\Scripts;$(PATH) + matrix: + - GENERATOR: "Visual Studio 16 2019" + PLATFORM: x64 + TRIPLET: x64-windows + CMAKE_OPTS: "-DBUILD_SHARED_LIBS=off" + CMAKE_CONFIG: Release + RUN_TESTS: yes + TOXENV: py311 + - GENERATOR: "Visual Studio 16 2019" + PLATFORM: x64 + TRIPLET: x64-uwp + CMAKE_OPTS: "-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0" + CMAKE_CONFIG: Release + RUN_TESTS: no + TOXENV: py311 + - GENERATOR: "Visual Studio 16 2019" + PLATFORM: Win32 + TRIPLET: x86-windows + CMAKE_OPTS: "-DBUILD_SHARED_LIBS=off" + CMAKE_CONFIG: Release + RUN_TESTS: yes + TOXENV: py311 + - GENERATOR: "Visual Studio 16 2019" + PLATFORM: Win32 + TRIPLET: x86-uwp + CMAKE_OPTS: "-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0" + CMAKE_CONFIG: Release + RUN_TESTS: no + TOXENV: py311 + - GENERATOR: "Visual Studio 16 2019" + PLATFORM: ARM + TRIPLET: arm-windows + CMAKE_OPTS: "-DENABLE_OPENSSL=off" + CMAKE_CONFIG: Release + RUN_TESTS: no + TOXENV: py311 + - GENERATOR: "Visual Studio 16 2019" + PLATFORM: ARM + TRIPLET: arm-uwp + CMAKE_OPTS: "-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DENABLE_OPENSSL=off" + CMAKE_CONFIG: Release + RUN_TESTS: no + TOXENV: py311 + - GENERATOR: "Visual Studio 16 2019" + PLATFORM: ARM64 + TRIPLET: arm64-windows + CMAKE_OPTS: "-DENABLE_OPENSSL=off" + CMAKE_CONFIG: Release + RUN_TESTS: no + TOXENV: py311 + - GENERATOR: "Visual Studio 16 2019" + PLATFORM: ARM64 + TRIPLET: arm64-uwp + CMAKE_OPTS: "-DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DENABLE_OPENSSL=off" + CMAKE_CONFIG: Release + RUN_TESTS: no + TOXENV: py311 + +before_build: + cmd: >- + py -m pip install nihtest + + vcpkg install zlib:%TRIPLET% bzip2:%TRIPLET% liblzma:%TRIPLET% zstd:%TRIPLET% + + mkdir build + + cd build + + cmake -DCMAKE_TOOLCHAIN_FILE=C:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake .. -G "%GENERATOR%" -A "%PLATFORM%" %CMAKE_OPTS% + + appveyor PushArtifact config.h + + appveyor PushArtifact CMakeCache.txt + +build_script: + cmd: >- + cmake --build . --config %CMAKE_CONFIG% --target INSTALL + + cmake --build . --config %CMAKE_CONFIG% + +test_script: + cmd: >- + set VERBOSE=yes + + IF %RUN_TESTS%==yes ( ctest -C %CMAKE_CONFIG% --output-on-failure ) diff --git a/core/deps/libzip/cmake-config.h.in b/core/deps/libzip/cmake-config.h.in index b234acba9..630ed23f6 100644 --- a/core/deps/libzip/cmake-config.h.in +++ b/core/deps/libzip/cmake-config.h.in @@ -4,6 +4,7 @@ #include "zipconf.h" #endif /* BEGIN DEFINES */ +#cmakedefine ENABLE_FDOPEN #cmakedefine HAVE___PROGNAME #cmakedefine HAVE__CLOSE #cmakedefine HAVE__DUP @@ -11,11 +12,12 @@ #cmakedefine HAVE__FILENO #cmakedefine HAVE__SETMODE #cmakedefine HAVE__SNPRINTF +#cmakedefine HAVE__SNPRINTF_S +#cmakedefine HAVE__SNWPRINTF_S #cmakedefine HAVE__STRDUP #cmakedefine HAVE__STRICMP #cmakedefine HAVE__STRTOI64 #cmakedefine HAVE__STRTOUI64 -#cmakedefine HAVE__UMASK #cmakedefine HAVE__UNLINK #cmakedefine HAVE_ARC4RANDOM #cmakedefine HAVE_CLONEFILE @@ -23,6 +25,7 @@ #cmakedefine HAVE_CRYPTO #cmakedefine HAVE_FICLONERANGE #cmakedefine HAVE_FILENO +#cmakedefine HAVE_FCHMOD #cmakedefine HAVE_FSEEKO #cmakedefine HAVE_FTELLO #cmakedefine HAVE_GETPROGNAME @@ -31,14 +34,21 @@ #cmakedefine HAVE_LIBLZMA #cmakedefine HAVE_LIBZSTD #cmakedefine HAVE_LOCALTIME_R +#cmakedefine HAVE_LOCALTIME_S +#cmakedefine HAVE_MEMCPY_S #cmakedefine HAVE_MBEDTLS #cmakedefine HAVE_MKSTEMP #cmakedefine HAVE_NULLABLE #cmakedefine HAVE_OPENSSL #cmakedefine HAVE_SETMODE +#cmakedefine HAVE_SNPRINTF +#cmakedefine HAVE_SNPRINTF_S #cmakedefine HAVE_STRCASECMP #cmakedefine HAVE_STRDUP +#cmakedefine HAVE_STRERROR_S +#cmakedefine HAVE_STRERRORLEN_S #cmakedefine HAVE_STRICMP +#cmakedefine HAVE_STRNCPY_S #cmakedefine HAVE_STRTOLL #cmakedefine HAVE_STRTOULL #cmakedefine HAVE_STRUCT_TM_TM_ZONE diff --git a/core/deps/libzip/cmake/Dist.cmake b/core/deps/libzip/cmake/Dist.cmake index 829112f44..d2adf9f6c 100644 --- a/core/deps/libzip/cmake/Dist.cmake +++ b/core/deps/libzip/cmake/Dist.cmake @@ -1,6 +1,6 @@ # Copyright (C) 2020 Dieter Baron and Thomas Klausner # -# The authors can be contacted at +# The authors can be contacted at # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -55,14 +55,14 @@ function(Dist ARCHIVE_NAME) if(NOT TARGET dist AND NOT TARGET distcheck) add_custom_target(dist COMMAND git config tar.tar.xz.command "xz -c" - COMMAND git archive --prefix=${ARCHIVE_NAME}/ -o ${ARCHIVE_NAME}.tar.gz HEAD - COMMAND git archive --prefix=${ARCHIVE_NAME}/ -o ${ARCHIVE_NAME}.tar.xz HEAD + COMMAND git archive --prefix=${ARCHIVE_NAME}/ -o ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.gz HEAD + COMMAND git archive --prefix=${ARCHIVE_NAME}/ -o ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.xz HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} ) add_custom_target(distcheck COMMAND chmod -R u+w ${ARCHIVE_NAME} ${ARCHIVE_NAME}-build ${ARCHIVE_NAME}-dest 2>/dev/null || true COMMAND rm -rf ${ARCHIVE_NAME} ${ARCHIVE_NAME}-build ${ARCHIVE_NAME}-dest - COMMAND ${CMAKE_COMMAND} -E tar xf ${ARCHIVE_NAME}.tar.gz + COMMAND ${CMAKE_COMMAND} -E tar xf ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.gz COMMAND chmod -R u-w ${ARCHIVE_NAME} COMMAND mkdir ${ARCHIVE_NAME}-build COMMAND mkdir ${ARCHIVE_NAME}-dest diff --git a/core/deps/libzip/cmake/FindMbedTLS.cmake b/core/deps/libzip/cmake/FindMbedTLS.cmake index 244b0c57c..5a6ef9d73 100644 --- a/core/deps/libzip/cmake/FindMbedTLS.cmake +++ b/core/deps/libzip/cmake/FindMbedTLS.cmake @@ -1,6 +1,6 @@ # Copyright (C) 2020 Dieter Baron and Thomas Klausner # -# The authors can be contacted at +# The authors can be contacted at # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions @@ -85,7 +85,16 @@ find_library(MbedTLS_LIBRARY # Extract version information from the header file if(MbedTLS_INCLUDE_DIR) - if(EXISTS ${MbedTLS_INCLUDE_DIR}/mbedtls/version.h) + # for major version 3 + if(EXISTS ${MbedTLS_INCLUDE_DIR}/mbedtls/build_info.h) + file(STRINGS ${MbedTLS_INCLUDE_DIR}/mbedtls/build_info.h _ver_line + REGEX "^#define MBEDTLS_VERSION_STRING *\"[0-9]+\\.[0-9]+\\.[0-9]+\"" + LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+" + MbedTLS_VERSION "${_ver_line}") + unset(_ver_line) + # for major version 2 + elseif(EXISTS ${MbedTLS_INCLUDE_DIR}/mbedtls/version.h) file(STRINGS ${MbedTLS_INCLUDE_DIR}/mbedtls/version.h _ver_line REGEX "^#define MBEDTLS_VERSION_STRING *\"[0-9]+\\.[0-9]+\\.[0-9]+\"" LIMIT_COUNT 1) diff --git a/core/deps/libzip/cmake/FindNettle.cmake b/core/deps/libzip/cmake/FindNettle.cmake index 982ac81ee..8f0deb014 100644 --- a/core/deps/libzip/cmake/FindNettle.cmake +++ b/core/deps/libzip/cmake/FindNettle.cmake @@ -1,6 +1,6 @@ # Copyright (C) 2020 Dieter Baron and Thomas Klausner # -# The authors can be contacted at +# The authors can be contacted at # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/cmake/FindZstd.cmake b/core/deps/libzip/cmake/FindZstd.cmake deleted file mode 100644 index a0da50381..000000000 --- a/core/deps/libzip/cmake/FindZstd.cmake +++ /dev/null @@ -1,135 +0,0 @@ -# Copyright (C) 2020 Dieter Baron and Thomas Klausner -# -# The authors can be contacted at -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# -# 3. The names of the authors may not be used to endorse or promote -# products derived from this software without specific prior -# written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS -# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -#[=======================================================================[.rst: -FindZstd -------- - -Finds the Zstandard (zstd) library. - -Imported Targets -^^^^^^^^^^^^^^^^ - -This module provides the following imported targets, if found: - -``Zstd::Zstd`` - The Zstandard library - -Result Variables -^^^^^^^^^^^^^^^^ - -This will define the following variables: - -``Zstd_FOUND`` - True if the system has the Zstandard library. -``Zstd_VERSION`` - The version of the Zstandard library which was found. -``Zstd_INCLUDE_DIRS`` - Include directories needed to use Zstandard. -``Zstd_LIBRARIES`` - Libraries needed to link to Zstandard. - -Cache Variables -^^^^^^^^^^^^^^^ - -The following cache variables may also be set: - -``Zstd_INCLUDE_DIR`` - The directory containing ``zstd.h``. -``Zstd_LIBRARY`` - The path to the Zstandard library. - -#]=======================================================================] - -find_package(PkgConfig) -pkg_check_modules(PC_Zstd QUIET zstd) - -find_path(Zstd_INCLUDE_DIR - NAMES zstd.h - PATHS ${PC_Zstd_INCLUDE_DIRS} -) -find_library(Zstd_LIBRARY - NAMES zstd - PATHS ${PC_Zstd_LIBRARY_DIRS} -) - -# Extract version information from the header file -if(Zstd_INCLUDE_DIR) - file(STRINGS ${Zstd_INCLUDE_DIR}/zstd.h _ver_major_line - REGEX "^#define ZSTD_VERSION_MAJOR *[0-9]+" - LIMIT_COUNT 1) - string(REGEX MATCH "[0-9]+" - Zstd_MAJOR_VERSION "${_ver_major_line}") - file(STRINGS ${Zstd_INCLUDE_DIR}/zstd.h _ver_minor_line - REGEX "^#define ZSTD_VERSION_MINOR *[0-9]+" - LIMIT_COUNT 1) - string(REGEX MATCH "[0-9]+" - Zstd_MINOR_VERSION "${_ver_minor_line}") - file(STRINGS ${Zstd_INCLUDE_DIR}/zstd.h _ver_release_line - REGEX "^#define ZSTD_VERSION_RELEASE *[0-9]+" - LIMIT_COUNT 1) - string(REGEX MATCH "[0-9]+" - Zstd_RELEASE_VERSION "${_ver_release_line}") - set(Zstd_VERSION "${Zstd_MAJOR_VERSION}.${Zstd_MINOR_VERSION}.${Zstd_RELEASE_VERSION}") - unset(_ver_major_line) - unset(_ver_minor_line) - unset(_ver_release_line) -endif() - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Zstd - FOUND_VAR Zstd_FOUND - REQUIRED_VARS - Zstd_LIBRARY - Zstd_INCLUDE_DIR - VERSION_VAR Zstd_VERSION -) - -if(Zstd_FOUND) - set(Zstd_LIBRARIES ${Zstd_LIBRARY}) - set(Zstd_INCLUDE_DIRS ${Zstd_INCLUDE_DIR}) - set(Zstd_DEFINITIONS ${PC_Zstd_CFLAGS_OTHER}) -endif() - -if(Zstd_FOUND AND NOT TARGET Zstd::Zstd) - add_library(Zstd::Zstd UNKNOWN IMPORTED) - set_target_properties(Zstd::Zstd PROPERTIES - IMPORTED_LOCATION "${Zstd_LIBRARY}" - INTERFACE_COMPILE_OPTIONS "${PC_Zstd_CFLAGS_OTHER}" - INTERFACE_INCLUDE_DIRECTORIES "${Zstd_INCLUDE_DIR}" - ) -endif() - -mark_as_advanced( - Zstd_INCLUDE_DIR - Zstd_LIBRARY -) diff --git a/core/deps/libzip/cmake/Findzstd.cmake b/core/deps/libzip/cmake/Findzstd.cmake new file mode 100644 index 000000000..b389c6b3d --- /dev/null +++ b/core/deps/libzip/cmake/Findzstd.cmake @@ -0,0 +1,186 @@ +# Copyright (C) 2020 Dieter Baron and Thomas Klausner +# +# The authors can be contacted at +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# 3. The names of the authors may not be used to endorse or promote +# products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#[=======================================================================[.rst: +Findzstd +------- + +Finds the Zstandard (zstd) library. + +Imported Targets +^^^^^^^^^^^^^^^^ + +This module provides the following imported targets, if found: + +``zstd::libzstd_shared`` + The shared Zstandard library +``zstd::libzstd_static`` + The shared Zstandard library + +Result Variables +^^^^^^^^^^^^^^^^ + +This will define the following variables: + +``zstd_FOUND`` + True if the system has the Zstandard library. +``zstd_VERSION`` + The version of the Zstandard library which was found. + +Cache Variables +^^^^^^^^^^^^^^^ + +The following cache variables may also be set: + +``zstd_INCLUDE_DIR`` + The directory containing ``zstd.h``. +``zstd_STATIC_LIBRARY`` + The path to the Zstandard static library. +``zstd_SHARED_LIBRARY`` + The path to the Zstandard shared library. +``zstd_DLL`` + The path to the Zstandard DLL. + +#]=======================================================================] + +find_package(PkgConfig) +pkg_check_modules(PC_zstd QUIET libzstd) + +find_path(zstd_INCLUDE_DIR + NAMES zstd.h + HINTS ${PC_zstd_INCLUDE_DIRS} +) + +find_file(zstd_DLL + NAMES libzstd.dll zstd.dll + PATH_SUFFIXES bin + HINTS ${PC_zstd_PREFIX} +) + +# On Windows, we manually define the library names to avoid mistaking the +# implib for the static library +if(zstd_DLL) + set(_zstd_win_static_name zstd-static) + set(_zstd_win_shared_name zstd) +else() + # vcpkg removes the -static suffix in static builds + set(_zstd_win_static_name zstd zstd_static) + set(_zstd_win_shared_name) +endif() + +set(_previous_suffixes ${CMAKE_FIND_LIBRARY_SUFFIXES}) +set(CMAKE_FIND_LIBRARY_SUFFIXES ".so" ".dylib" ".dll.a" ".lib") +find_library(zstd_SHARED_LIBRARY + NAMES zstd ${_zstd_win_shared_name} + HINTS ${PC_zstd_LIBDIR} +) + +set(CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib") +find_library(zstd_STATIC_LIBRARY + NAMES zstd ${_zstd_win_static_name} + HINTS ${PC_zstd_LIBDIR} +) +set(CMAKE_FIND_LIBRARY_SUFFIXES ${_previous_suffixes}) + +# Set zstd_LIBRARY to the shared library or fall back to the static library +if(zstd_SHARED_LIBRARY) + set(_zstd_LIBRARY ${zstd_SHARED_LIBRARY}) +else() + set(_zstd_LIBRARY ${zstd_STATIC_LIBRARY}) +endif() + +# Extract version information from the header file +if(zstd_INCLUDE_DIR) + file(STRINGS ${zstd_INCLUDE_DIR}/zstd.h _ver_major_line + REGEX "^#define ZSTD_VERSION_MAJOR *[0-9]+" + LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+" + zstd_MAJOR_VERSION "${_ver_major_line}") + file(STRINGS ${zstd_INCLUDE_DIR}/zstd.h _ver_minor_line + REGEX "^#define ZSTD_VERSION_MINOR *[0-9]+" + LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+" + zstd_MINOR_VERSION "${_ver_minor_line}") + file(STRINGS ${zstd_INCLUDE_DIR}/zstd.h _ver_release_line + REGEX "^#define ZSTD_VERSION_RELEASE *[0-9]+" + LIMIT_COUNT 1) + string(REGEX MATCH "[0-9]+" + zstd_RELEASE_VERSION "${_ver_release_line}") + set(Zstd_VERSION "${zstd_MAJOR_VERSION}.${zstd_MINOR_VERSION}.${zstd_RELEASE_VERSION}") + unset(_ver_major_line) + unset(_ver_minor_line) + unset(_ver_release_line) +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(zstd + FOUND_VAR zstd_FOUND + REQUIRED_VARS + _zstd_LIBRARY + zstd_INCLUDE_DIR + VERSION_VAR zstd_VERSION +) + +if(zstd_FOUND AND zstd_SHARED_LIBRARY AND NOT TARGET zstd::libzstd_shared) + add_library(zstd::libzstd_shared SHARED IMPORTED) + if(WIN32) + set_target_properties(zstd::libzstd_shared PROPERTIES + IMPORTED_LOCATION "${zstd_DLL}" + IMPORTED_IMPLIB "${zstd_SHARED_LIBRARY}" + ) + else() + set_target_properties(zstd::libzstd_shared PROPERTIES + IMPORTED_LOCATION "${zstd_SHARED_LIBRARY}" + ) + endif() + + set_target_properties(zstd::libzstd_shared PROPERTIES + INTERFACE_COMPILE_OPTIONS "${PC_zstd_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}" + ) +endif() + +if(zstd_FOUND AND zstd_STATIC_LIBRARY AND NOT TARGET zstd::libzstd_static) + add_library(zstd::libzstd_static STATIC IMPORTED) + set_target_properties(zstd::libzstd_static PROPERTIES + IMPORTED_LOCATION "${zstd_STATIC_LIBRARY}" + INTERFACE_COMPILE_OPTIONS "${PC_zstd_CFLAGS_OTHER}" + INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}" + ) +endif() + +mark_as_advanced( + zstd_INCLUDE_DIR + zstd_DLL + zstd_SHARED_LIBRARY + zstd_STATIC_LIBRARY +) diff --git a/core/deps/libzip/cmake/GenerateZipErrorStrings.cmake b/core/deps/libzip/cmake/GenerateZipErrorStrings.cmake new file mode 100644 index 000000000..550412b6c --- /dev/null +++ b/core/deps/libzip/cmake/GenerateZipErrorStrings.cmake @@ -0,0 +1,47 @@ +# create zip_err_str.c from zip.h and zipint.h +file(READ ${PROJECT_SOURCE_DIR}/lib/zip.h zip_h) +string(REGEX MATCHALL "#define ZIP_ER_([A-Z0-9_]+) ([0-9]+)[ \t]+/([-*0-9a-zA-Z, ']*)/" zip_h_err ${zip_h}) +file(READ ${PROJECT_SOURCE_DIR}/lib/zipint.h zipint_h) +string(REGEX MATCHALL "#define ZIP_ER_DETAIL_([A-Z0-9_]+) ([0-9]+)[ \t]+/([-*0-9a-zA-Z, ']*)/" zipint_h_err ${zipint_h}) +set(zip_err_str [=[ +/* + This file was generated automatically by CMake + from zip.h and zipint.h\; make changes there. +*/ + +#include "zipint.h" + +#define L ZIP_ET_LIBZIP +#define N ZIP_ET_NONE +#define S ZIP_ET_SYS +#define Z ZIP_ET_ZLIB + +#define E ZIP_DETAIL_ET_ENTRY +#define G ZIP_DETAIL_ET_GLOBAL + +const struct _zip_err_info _zip_err_str[] = { +]=]) +set(zip_err_type) +foreach(errln ${zip_h_err}) + string(REGEX MATCH "#define ZIP_ER_([A-Z0-9_]+) ([0-9]+)[ \t]+/([-*0-9a-zA-Z, ']*)/" err_t_tt ${errln}) + string(REGEX MATCH "([L|N|S|Z]+) ([-0-9a-zA-Z,, ']*)" err_t_tt "${CMAKE_MATCH_3}") + string(STRIP "${CMAKE_MATCH_2}" err_t_tt) + string(APPEND zip_err_str " { ${CMAKE_MATCH_1}, \"${err_t_tt}\" },\n") +endforeach() +string(APPEND zip_err_str [=[}\; + +const int _zip_err_str_count = sizeof(_zip_err_str)/sizeof(_zip_err_str[0])\; + +const struct _zip_err_info _zip_err_details[] = { +]=]) +foreach(errln ${zipint_h_err}) + string(REGEX MATCH "#define ZIP_ER_DETAIL_([A-Z0-9_]+) ([0-9]+)[ \t]+/([-*0-9a-zA-Z, ']*)/" err_t_tt ${errln}) + string(REGEX MATCH "([E|G]+) ([-0-9a-zA-Z, ']*)" err_t_tt "${CMAKE_MATCH_3}") + string(STRIP "${CMAKE_MATCH_2}" err_t_tt) + string(APPEND zip_err_str " { ${CMAKE_MATCH_1}, \"${err_t_tt}\" },\n") +endforeach() +string(APPEND zip_err_str [=[}\; + +const int _zip_err_details_count = sizeof(_zip_err_details)/sizeof(_zip_err_details[0])\; +]=]) +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/zip_err_str.c ${zip_err_str}) diff --git a/core/deps/libzip/developer-xcode/Info.plist b/core/deps/libzip/developer-xcode/Info.plist deleted file mode 100644 index 4cc3a77e5..000000000 --- a/core/deps/libzip/developer-xcode/Info.plist +++ /dev/null @@ -1,46 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleExecutable - ${EXECUTABLE_NAME} - CFBundleIconFile - - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - ${PRODUCT_NAME} - CFBundlePackageType - BNDL - CFBundleShortVersionString - PACKAGE_VERSION - CFBundleSignature - ???? - CFBundleVersion - 1 - CFPlugInDynamicRegisterFunction - - CFPlugInDynamicRegistration - NO - CFPlugInFactories - - 00000000-0000-0000-0000-000000000000 - MyFactoryFunction - - CFPlugInTypes - - 00000000-0000-0000-0000-000000000000 - - 00000000-0000-0000-0000-000000000000 - - - CFPlugInUnloadFunction - - NSHumanReadableCopyright - Copyright © 2014 Dieter Baron and Thomas Klausner - - diff --git a/core/deps/libzip/developer-xcode/README Xcode Project.md b/core/deps/libzip/developer-xcode/README Xcode Project.md deleted file mode 100644 index 8ae1f65bb..000000000 --- a/core/deps/libzip/developer-xcode/README Xcode Project.md +++ /dev/null @@ -1,6 +0,0 @@ -This Xcode project is for development only, it is not meant to -compile production builds. - -It is used internally to develop libzip and to run Xcode's diagnostic -tools on the source, and is not always kept up-to-date. Please use -the standard build method instead. diff --git a/core/deps/libzip/developer-xcode/config.h b/core/deps/libzip/developer-xcode/config.h deleted file mode 100644 index a89b2fe2f..000000000 --- a/core/deps/libzip/developer-xcode/config.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef HAD_CONFIG_H -#define HAD_CONFIG_H -#ifndef _HAD_ZIPCONF_H -#include "zipconf.h" -#endif -/* BEGIN DEFINES */ -/* #undef HAVE___PROGNAME */ -/* #undef HAVE__CHMOD */ -/* #undef HAVE__CLOSE */ -/* #undef HAVE__DUP */ -/* #undef HAVE__FDOPEN */ -/* #undef HAVE__FILENO */ -/* #undef HAVE__OPEN */ -/* #undef HAVE__SETMODE */ -/* #undef HAVE__SNPRINTF */ -/* #undef HAVE__STRDUP */ -/* #undef HAVE__STRICMP */ -/* #undef HAVE__STRTOI64 */ -/* #undef HAVE__STRTOUI64 */ -/* #undef HAVE__UMASK */ -/* #undef HAVE__UNLINK */ -#define HAVE_ARC4RANDOM -#define HAVE_CLONEFILE -#define HAVE_COMMONCRYPTO -#define HAVE_CRYPTO -/* #undef HAVE_FICLONERANGE */ -#define HAVE_FILENO -#define HAVE_FSEEKO -#define HAVE_FTELLO -#define HAVE_GETPROGNAME -/* #undef HAVE_GNUTLS */ -#define HAVE_LIBBZ2 -#define HAVE_LIBLZMA -#define HAVE_LOCALTIME_R -/* #undef HAVE_MBEDTLS */ -/* #undef HAVE_MKSTEMP */ -#define HAVE_NULLABLE -#define HAVE_OPEN -/* #undef HAVE_OPENSSL */ -#define HAVE_SETMODE -#define HAVE_SSIZE_T_LIBZIP -#define HAVE_STRCASECMP -#define HAVE_STRDUP -/* #undef HAVE_STRICMP */ -#define HAVE_STRTOLL -#define HAVE_STRTOULL -/* #undef HAVE_STRUCT_TM_TM_ZONE */ -#define HAVE_STDBOOL_H -#define HAVE_STRINGS_H -#define HAVE_UNISTD_H -/* #undef HAVE_WINDOWS_CRYPTO */ -/* #undef __INT8_LIBZIP */ -#define INT8_T_LIBZIP 1 -#define UINT8_T_LIBZIP 1 -/* #undef __INT16_LIBZIP */ -#define INT16_T_LIBZIP 2 -#define UINT16_T_LIBZIP 2 -/* #undef __INT32_LIBZIP */ -#define INT32_T_LIBZIP 4 -#define UINT32_T_LIBZIP 4 -/* #undef __INT64_LIBZIP */ -#define INT64_T_LIBZIP 8 -#define UINT64_T_LIBZIP 8 -#define SHORT_LIBZIP 2 -#define INT_LIBZIP 4 -#define LONG_LIBZIP 8 -#define LONG_LONG_LIBZIP 8 -#define SIZEOF_OFF_T 8 -#define SIZE_T_LIBZIP 8 -#define SSIZE_T_LIBZIP 8 -/* #undef HAVE_DIRENT_H */ -#define HAVE_FTS_H -/* #undef HAVE_NDIR_H */ -/* #undef HAVE_SYS_DIR_H */ -/* #undef HAVE_SYS_NDIR_H */ -/* #undef WORDS_BIGENDIAN */ -#define HAVE_SHARED -/* END DEFINES */ -#define PACKAGE "libzip" -#define VERSION "1.5.2a" - -#ifndef HAVE_SSIZE_T_LIBZIP -#if SIZE_T_LIBZIP == INT_LIBZIP -typedef int ssize_t; -#elif SIZE_T_LIBZIP == LONG_LIBZIP -typedef long ssize_t; -#elif SIZE_T_LIBZIP == LONG_LONG_LIBZIP -typedef long long ssize_t; -#else -#error no suitable type for ssize_t found -#endif -#endif - -#endif /* HAD_CONFIG_H */ diff --git a/core/deps/libzip/developer-xcode/extract-version.sh b/core/deps/libzip/developer-xcode/extract-version.sh deleted file mode 100644 index 85e83fcb3..000000000 --- a/core/deps/libzip/developer-xcode/extract-version.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# Replace the value for PLIST_KEY with the resolved definition from the header file that was passed in. - -SOURCE_HEADER_FILE_PATH=$1 -SOURCE_PLIST_PATH=$2 - -PLIST_KEY="CFBundleShortVersionString" - -VERSION_KEY=`/usr/libexec/PlistBuddy -c "Print :${PLIST_KEY}" "${SOURCE_PLIST_PATH}"` - -#echo "Key: ${VERSION_KEY}" - -VERSION_NUM=`cat "${SOURCE_HEADER_FILE_PATH}" | sed -n "s|#define ${VERSION_KEY} \"\(.*\)\".*|\1|p"` - -#echo "Value: ${VERSION_NUM}" - -TARGET_PLIST_PATH="${TARGET_BUILD_DIR}/${INFOPLIST_PATH}" - -/usr/libexec/PlistBuddy -c "Set :${PLIST_KEY} ${VERSION_NUM}" "${TARGET_PLIST_PATH}" diff --git a/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.pbxproj b/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.pbxproj deleted file mode 100644 index 8e7d23b2f..000000000 --- a/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.pbxproj +++ /dev/null @@ -1,2934 +0,0 @@ -// !$*UTF8*$! -{ - archiveVersion = 1; - classes = { - }; - objectVersion = 46; - objects = { - -/* Begin PBXAggregateTarget section */ - 4B01D72815B2F5A2002D5007 /* command line tools */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 4B01D72915B2F5A2002D5007 /* Build configuration list for PBXAggregateTarget "command line tools" */; - buildPhases = ( - ); - dependencies = ( - 4B2CADAC1C50D57800291DE6 /* PBXTargetDependency */, - 4B01D72D15B2F5AC002D5007 /* PBXTargetDependency */, - 4B01D72F15B2F5AC002D5007 /* PBXTargetDependency */, - ); - name = "command line tools"; - productName = "command line tools"; - }; - 4B54447915C977A20067BA33 /* all */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 4B54447C15C977A20067BA33 /* Build configuration list for PBXAggregateTarget "all" */; - buildPhases = ( - ); - dependencies = ( - 4BCF6A7B1C3BDDFF00F036E9 /* PBXTargetDependency */, - 4B54447F15C977AF0067BA33 /* PBXTargetDependency */, - 4B54448115C977B10067BA33 /* PBXTargetDependency */, - ); - name = all; - productName = all; - }; - 4BACD5A715BC2D8200920691 /* test programs */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 4BACD5AE15BC2D8200920691 /* Build configuration list for PBXAggregateTarget "test programs" */; - buildPhases = ( - ); - dependencies = ( - 4BFF2B531FE13002006EF3F3 /* PBXTargetDependency */, - 4BD6CB6E19E71D0800710654 /* PBXTargetDependency */, - 4BACD65515BC303B00920691 /* PBXTargetDependency */, - 4BACD65715BC303B00920691 /* PBXTargetDependency */, - 4BACD65915BC303B00920691 /* PBXTargetDependency */, - 4BACD66915BC303B00920691 /* PBXTargetDependency */, - 4B51DDC51FDAE2F000C5CA85 /* PBXTargetDependency */, - ); - name = "test programs"; - productName = "test programs"; - }; - 4BCF6A681C3BDDD500F036E9 /* examples */ = { - isa = PBXAggregateTarget; - buildConfigurationList = 4BCF6A751C3BDDD500F036E9 /* Build configuration list for PBXAggregateTarget "examples" */; - buildPhases = ( - ); - dependencies = ( - 4BCF6A791C3BDDF900F036E9 /* PBXTargetDependency */, - ); - name = examples; - productName = examples; - }; -/* End PBXAggregateTarget section */ - -/* Begin PBXBuildFile section */ - 3D7E35431B33063F00022624 /* in-memory.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D7E35401B33063600022624 /* in-memory.c */; }; - 3D7E35461B33064B00022624 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 3D7E35481B33076C00022624 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 3D7E35471B33076C00022624 /* libz.dylib */; }; - 3D7E35491B330AD500022624 /* zip_source_is_deleted.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BE402AC19D94AE400298248 /* zip_source_is_deleted.c */; }; - 3D9284821C309510001EABA7 /* zip_hash.c in Sources */ = {isa = PBXBuildFile; fileRef = 3D9284801C309510001EABA7 /* zip_hash.c */; }; - 4B00CA24242F59D700E0B71C /* zip_source_pkware_decode.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B00CA21242F59D700E0B71C /* zip_source_pkware_decode.c */; }; - 4B00CA25242F59D700E0B71C /* zip_pkware.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B00CA22242F59D700E0B71C /* zip_pkware.c */; }; - 4B00CA26242F59D700E0B71C /* zip_source_pkware_encode.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B00CA23242F59D700E0B71C /* zip_source_pkware_encode.c */; }; - 4B00CA2D242F5C2500E0B71C /* set_file_dostime.test in Resources */ = {isa = PBXBuildFile; fileRef = 4B00CA27242F5C2500E0B71C /* set_file_dostime.test */; }; - 4B00CA2E242F5C2500E0B71C /* cancel_45.test in Resources */ = {isa = PBXBuildFile; fileRef = 4B00CA28242F5C2500E0B71C /* cancel_45.test */; }; - 4B00CA2F242F5C2500E0B71C /* set_compression_store_to_xz.test in Resources */ = {isa = PBXBuildFile; fileRef = 4B00CA29242F5C2500E0B71C /* set_compression_store_to_xz.test */; }; - 4B00CA30242F5C2500E0B71C /* encryption-nonrandom-pkware.test in Resources */ = {isa = PBXBuildFile; fileRef = 4B00CA2A242F5C2500E0B71C /* encryption-nonrandom-pkware.test */; }; - 4B00CA31242F5C2500E0B71C /* cancel_90.test in Resources */ = {isa = PBXBuildFile; fileRef = 4B00CA2B242F5C2500E0B71C /* cancel_90.test */; }; - 4B00CA32242F5C2500E0B71C /* set_compression_xz_to_store.test in Resources */ = {isa = PBXBuildFile; fileRef = 4B00CA2C242F5C2500E0B71C /* set_compression_xz_to_store.test */; }; - 4B01D6A615B2F46B002D5007 /* zip_add_dir.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F115B1B25E00236D3C /* zip_add_dir.c */; }; - 4B01D6A715B2F46B002D5007 /* zip_add_entry.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F215B1B25E00236D3C /* zip_add_entry.c */; }; - 4B01D6A815B2F46B002D5007 /* zip_add.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F315B1B25E00236D3C /* zip_add.c */; }; - 4B01D6A915B2F46B002D5007 /* zip_close.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F415B1B25E00236D3C /* zip_close.c */; }; - 4B01D6AA15B2F46B002D5007 /* zip_delete.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F515B1B25E00236D3C /* zip_delete.c */; }; - 4B01D6AB15B2F46B002D5007 /* zip_dir_add.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F615B1B25E00236D3C /* zip_dir_add.c */; }; - 4B01D6AC15B2F46B002D5007 /* zip_dirent.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F715B1B25E00236D3C /* zip_dirent.c */; }; - 4B01D6AD15B2F46B002D5007 /* zip_discard.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F815B1B25E00236D3C /* zip_discard.c */; }; - 4B01D6AE15B2F46B002D5007 /* zip_entry.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71F915B1B25E00236D3C /* zip_entry.c */; }; - 4B01D6B015B2F46B002D5007 /* zip_error_clear.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71FB15B1B25E00236D3C /* zip_error_clear.c */; }; - 4B01D6B115B2F46B002D5007 /* zip_error_get_sys_type.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71FC15B1B25E00236D3C /* zip_error_get_sys_type.c */; }; - 4B01D6B215B2F46B002D5007 /* zip_error_get.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71FD15B1B25E00236D3C /* zip_error_get.c */; }; - 4B01D6B315B2F46B002D5007 /* zip_error_strerror.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71FE15B1B25E00236D3C /* zip_error_strerror.c */; }; - 4B01D6B415B2F46B002D5007 /* zip_error_to_str.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC71FF15B1B25E00236D3C /* zip_error_to_str.c */; }; - 4B01D6B515B2F46B002D5007 /* zip_error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720015B1B25E00236D3C /* zip_error.c */; }; - 4B01D6B615B2F46B002D5007 /* zip_extra_field_api.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720115B1B25E00236D3C /* zip_extra_field_api.c */; }; - 4B01D6B715B2F46B002D5007 /* zip_extra_field.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720215B1B25E00236D3C /* zip_extra_field.c */; }; - 4B01D6B815B2F46B002D5007 /* zip_fclose.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720315B1B25E00236D3C /* zip_fclose.c */; }; - 4B01D6B915B2F46B002D5007 /* zip_fdopen.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720415B1B25E00236D3C /* zip_fdopen.c */; }; - 4B01D6BA15B2F46B002D5007 /* zip_file_add.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720515B1B25E00236D3C /* zip_file_add.c */; }; - 4B01D6BB15B2F46B002D5007 /* zip_file_error_clear.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720615B1B25E00236D3C /* zip_file_error_clear.c */; }; - 4B01D6BC15B2F46B002D5007 /* zip_file_error_get.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720715B1B25E00236D3C /* zip_file_error_get.c */; }; - 4B01D6BD15B2F46B002D5007 /* zip_file_get_comment.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720815B1B25E00236D3C /* zip_file_get_comment.c */; }; - 4B01D6BE15B2F46B002D5007 /* zip_file_get_offset.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720915B1B25E00236D3C /* zip_file_get_offset.c */; }; - 4B01D6BF15B2F46B002D5007 /* zip_file_rename.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720A15B1B25E00236D3C /* zip_file_rename.c */; }; - 4B01D6C015B2F46B002D5007 /* zip_file_replace.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720B15B1B25E00236D3C /* zip_file_replace.c */; }; - 4B01D6C115B2F46B002D5007 /* zip_file_set_comment.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720C15B1B25E00236D3C /* zip_file_set_comment.c */; }; - 4B01D6C215B2F46B002D5007 /* zip_file_strerror.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720D15B1B25E00236D3C /* zip_file_strerror.c */; }; - 4B01D6C415B2F46B002D5007 /* zip_fopen_encrypted.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC720F15B1B25E00236D3C /* zip_fopen_encrypted.c */; }; - 4B01D6C515B2F46B002D5007 /* zip_fopen_index_encrypted.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721015B1B25E00236D3C /* zip_fopen_index_encrypted.c */; }; - 4B01D6C615B2F46B002D5007 /* zip_fopen_index.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721115B1B25E00236D3C /* zip_fopen_index.c */; }; - 4B01D6C715B2F46B002D5007 /* zip_fopen.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721215B1B25E00236D3C /* zip_fopen.c */; }; - 4B01D6C815B2F46B002D5007 /* zip_fread.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721315B1B25E00236D3C /* zip_fread.c */; }; - 4B01D6C915B2F46B002D5007 /* zip_get_archive_comment.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721415B1B25E00236D3C /* zip_get_archive_comment.c */; }; - 4B01D6CA15B2F46B002D5007 /* zip_get_archive_flag.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721515B1B25E00236D3C /* zip_get_archive_flag.c */; }; - 4B01D6CC15B2F46B002D5007 /* zip_get_encryption_implementation.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721715B1B25E00236D3C /* zip_get_encryption_implementation.c */; }; - 4B01D6CD15B2F46B002D5007 /* zip_get_file_comment.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721815B1B25E00236D3C /* zip_get_file_comment.c */; }; - 4B01D6CE15B2F46B002D5007 /* zip_get_name.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721915B1B25E00236D3C /* zip_get_name.c */; }; - 4B01D6CF15B2F46B002D5007 /* zip_get_num_entries.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721A15B1B25E00236D3C /* zip_get_num_entries.c */; }; - 4B01D6D015B2F46B002D5007 /* zip_get_num_files.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721B15B1B25E00236D3C /* zip_get_num_files.c */; }; - 4B01D6D115B2F46B002D5007 /* zip_memdup.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721C15B1B25E00236D3C /* zip_memdup.c */; }; - 4B01D6D215B2F46B002D5007 /* zip_name_locate.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721D15B1B25E00236D3C /* zip_name_locate.c */; }; - 4B01D6D315B2F46B002D5007 /* zip_new.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721E15B1B25E00236D3C /* zip_new.c */; }; - 4B01D6D415B2F46B002D5007 /* zip_open.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC721F15B1B25E00236D3C /* zip_open.c */; }; - 4B01D6D515B2F46B002D5007 /* zip_rename.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722015B1B25E00236D3C /* zip_rename.c */; }; - 4B01D6D615B2F46B002D5007 /* zip_replace.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722115B1B25E00236D3C /* zip_replace.c */; }; - 4B01D6D715B2F46B002D5007 /* zip_set_archive_comment.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722215B1B25E00236D3C /* zip_set_archive_comment.c */; }; - 4B01D6D815B2F46B002D5007 /* zip_set_archive_flag.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722315B1B25E00236D3C /* zip_set_archive_flag.c */; }; - 4B01D6D915B2F46B002D5007 /* zip_set_default_password.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722415B1B25E00236D3C /* zip_set_default_password.c */; }; - 4B01D6DA15B2F46B002D5007 /* zip_set_file_comment.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722515B1B25E00236D3C /* zip_set_file_comment.c */; }; - 4B01D6DB15B2F46B002D5007 /* zip_set_file_compression.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722615B1B25E00236D3C /* zip_set_file_compression.c */; }; - 4B01D6DC15B2F46B002D5007 /* zip_set_name.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722715B1B25E00236D3C /* zip_set_name.c */; }; - 4B01D6DD15B2F46B002D5007 /* zip_source_buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722815B1B25E00236D3C /* zip_source_buffer.c */; }; - 4B01D6DE15B2F46B002D5007 /* zip_source_close.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722915B1B25E00236D3C /* zip_source_close.c */; }; - 4B01D6DF15B2F46B002D5007 /* zip_source_crc.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722A15B1B25E00236D3C /* zip_source_crc.c */; }; - 4B01D6E115B2F46B002D5007 /* zip_source_error.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722C15B1B25E00236D3C /* zip_source_error.c */; }; - 4B01D6E415B2F46B002D5007 /* zip_source_free.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC722F15B1B25E00236D3C /* zip_source_free.c */; }; - 4B01D6E515B2F46B002D5007 /* zip_source_function.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723015B1B25E00236D3C /* zip_source_function.c */; }; - 4B01D6E615B2F46B002D5007 /* zip_source_layered.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723115B1B25E00236D3C /* zip_source_layered.c */; }; - 4B01D6E715B2F46B002D5007 /* zip_source_open.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723215B1B25E00236D3C /* zip_source_open.c */; }; - 4B01D6EA15B2F46B002D5007 /* zip_source_read.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723515B1B25E00236D3C /* zip_source_read.c */; }; - 4B01D6EB15B2F46B002D5007 /* zip_source_stat.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723615B1B25E00236D3C /* zip_source_stat.c */; }; - 4B01D6EC15B2F46B002D5007 /* zip_source_window.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723715B1B25E00236D3C /* zip_source_window.c */; }; - 4B01D6ED15B2F46B002D5007 /* zip_source_zip_new.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723815B1B25E00236D3C /* zip_source_zip_new.c */; }; - 4B01D6EE15B2F46B002D5007 /* zip_source_zip.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723915B1B25E00236D3C /* zip_source_zip.c */; }; - 4B01D6EF15B2F46B002D5007 /* zip_stat_index.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723A15B1B25E00236D3C /* zip_stat_index.c */; }; - 4B01D6F015B2F46B002D5007 /* zip_stat_init.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723B15B1B25E00236D3C /* zip_stat_init.c */; }; - 4B01D6F115B2F46B002D5007 /* zip_stat.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723C15B1B25E00236D3C /* zip_stat.c */; }; - 4B01D6F215B2F46B002D5007 /* zip_strerror.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723D15B1B25E00236D3C /* zip_strerror.c */; }; - 4B01D6F315B2F46B002D5007 /* zip_string.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723E15B1B25E00236D3C /* zip_string.c */; }; - 4B01D6F415B2F46B002D5007 /* zip_unchange_all.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC723F15B1B25E00236D3C /* zip_unchange_all.c */; }; - 4B01D6F515B2F46B002D5007 /* zip_unchange_archive.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC724015B1B25E00236D3C /* zip_unchange_archive.c */; }; - 4B01D6F615B2F46B002D5007 /* zip_unchange_data.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC724115B1B25E00236D3C /* zip_unchange_data.c */; }; - 4B01D6F715B2F46B002D5007 /* zip_unchange.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC724215B1B25E00236D3C /* zip_unchange.c */; }; - 4B01D6F815B2F46B002D5007 /* zip_utf-8.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BDC724315B1B25E00236D3C /* zip_utf-8.c */; }; - 4B01D70715B2F4C5002D5007 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4B01D70915B2F4CF002D5007 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4B01D70D15B2F4EB002D5007 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4B01D70E15B2F4EB002D5007 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4B01D72515B2F57B002D5007 /* zipcmp.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B01D72115B2F572002D5007 /* zipcmp.c */; }; - 4B01D72615B2F57F002D5007 /* zipmerge.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B01D72215B2F572002D5007 /* zipmerge.c */; }; - 4B01D73215B2F5EE002D5007 /* zipconf.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BDC729E15B1B4E900236D3C /* zipconf.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 4B01D73C15B2F6AF002D5007 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4B0454BA1E8E3E08002FA1F9 /* zip_source_compress.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B0454B61E8E3DF7002FA1F9 /* zip_source_compress.c */; }; - 4B0454BC1E8E3E24002FA1F9 /* zip_algorithm_bzip2.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B0454B41E8E3DF7002FA1F9 /* zip_algorithm_bzip2.c */; }; - 4B0454BD1E8E3E24002FA1F9 /* zip_algorithm_deflate.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B0454B51E8E3DF7002FA1F9 /* zip_algorithm_deflate.c */; }; - 4B3A5F521DF96EB4005A53A1 /* zip_fseek.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B3A5F4D1DF96D83005A53A1 /* zip_fseek.c */; }; - 4B3A5F531DF96EB4005A53A1 /* zip_ftell.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B3A5F4E1DF96D83005A53A1 /* zip_ftell.c */; }; - 4B3FAE802385C5CC00192D6A /* zip_algorithm_xz.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B3FAE7F2385C5A300192D6A /* zip_algorithm_xz.c */; }; - 4B3FAE822385C79200192D6A /* liblzma.5.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B3FAE812385C79200192D6A /* liblzma.5.dylib */; }; - 4B5169A822A7993E00AA4340 /* zip_mkstempm.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B5169A722A7993D00AA4340 /* zip_mkstempm.c */; }; - 4B51DDBA1FDAE20A00C5CA85 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4B51DDBB1FDAE20A00C5CA85 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4B51DDC11FDAE25B00C5CA85 /* ziptool.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BACD57C15BC2AEF00920691 /* ziptool.c */; }; - 4B51DDC21FDAE25F00C5CA85 /* ziptool_regress.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B51DDB31FDAE1DB00C5CA85 /* ziptool_regress.c */; }; - 4B51DDC31FDAE26600C5CA85 /* source_hole.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD6CB5C19E6A5D900710654 /* source_hole.c */; }; - 4B542C2C22B12E3900960B38 /* zip_random_unix.c in Sources */ = {isa = PBXBuildFile; fileRef = 736ED9B81E3D688C00C36873 /* zip_random_unix.c */; }; - 4B5D0CD5244B154E006C2E93 /* zip_source_get_file_attributes.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B5D0CD4244B154E006C2E93 /* zip_source_get_file_attributes.c */; }; - 4B69E6EE2032F18B0001EEE7 /* zip_winzip_aes.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B69E6ED2032F1870001EEE7 /* zip_winzip_aes.c */; }; - 4B82CED519915F360097BC18 /* zip_file_set_mtime.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B82CED319915F360097BC18 /* zip_file_set_mtime.c */; }; - 4B908F532385BE6D00886355 /* zip_libzip_version.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B908F502385BE6C00886355 /* zip_libzip_version.c */; }; - 4B908F552385BE6D00886355 /* zip_source_accept_empty.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B908F522385BE6D00886355 /* zip_source_accept_empty.c */; }; - 4B93995A24631B3E00AEBDA4 /* zip_source_file_stdio_named.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B93995524631B3D00AEBDA4 /* zip_source_file_stdio_named.c */; }; - 4B93995B24631B3E00AEBDA4 /* zip_source_file_common.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B93995624631B3E00AEBDA4 /* zip_source_file_common.c */; }; - 4B93995C24631B3E00AEBDA4 /* zip_source_file_stdio.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B93995724631B3E00AEBDA4 /* zip_source_file_stdio.c */; }; - 4B93995D24631B3E00AEBDA4 /* zip_source_file_stdio.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B93995824631B3E00AEBDA4 /* zip_source_file_stdio.h */; }; - 4B93995E24631B3E00AEBDA4 /* zip_source_file.h in Headers */ = {isa = PBXBuildFile; fileRef = 4B93995924631B3E00AEBDA4 /* zip_source_file.h */; }; - 4B972050188EBE85002FAFAD /* zip_file_get_external_attributes.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B97204D188EBE85002FAFAD /* zip_file_get_external_attributes.c */; }; - 4B972052188EBE85002FAFAD /* zip_file_set_external_attributes.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B97204E188EBE85002FAFAD /* zip_file_set_external_attributes.c */; }; - 4B9E577C24C7202000CEE0D6 /* zip_algorithm_zstd.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B9E577A24C7026B00CEE0D6 /* zip_algorithm_zstd.c */; }; - 4B9E578824C9770C00CEE0D6 /* libzstd.1.4.5.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B9E578724C9770C00CEE0D6 /* libzstd.1.4.5.dylib */; }; - 4B9E578A24C9779900CEE0D6 /* zip_err_str.c in Sources */ = {isa = PBXBuildFile; fileRef = 4B9E578924C9779900CEE0D6 /* zip_err_str.c */; }; - 4BACD59315BC2CFA00920691 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4BACD59415BC2D0800920691 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4BACD5BB15BC2DC900920691 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4BACD5BC15BC2DC900920691 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4BACD5C315BC2DE000920691 /* add_from_filep.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BACD57715BC2AEF00920691 /* add_from_filep.c */; }; - 4BACD5CA15BC2DF200920691 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4BACD5CB15BC2DF200920691 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4BACD5D215BC2EFE00920691 /* fopen_unchanged.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BACD57A15BC2AEF00920691 /* fopen_unchanged.c */; }; - 4BACD5D915BC2F3700920691 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4BACD5DA15BC2F3700920691 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4BACD5E115BC2F4500920691 /* fread.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BACD57B15BC2AEF00920691 /* fread.c */; }; - 4BACD64A15BC301300920691 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4BACD64B15BC301300920691 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4BACD65315BC302500920691 /* tryopen.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BACD58415BC2AEF00920691 /* tryopen.c */; }; - 4BC03FA41FDD6B6F003C7B62 /* zip_source_begin_write_cloning.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BC03FA21FDD6B6F003C7B62 /* zip_source_begin_write_cloning.c */; }; - 4BCB434319E9347E0067FAA3 /* zip_buffer.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCB434119E9347E0067FAA3 /* zip_buffer.c */; }; - 4BCF3022199A2F820064207B /* zip_io_util.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF3019199A2F820064207B /* zip_io_util.c */; }; - 4BCF3024199A2F820064207B /* zip_source_begin_write.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF301A199A2F820064207B /* zip_source_begin_write.c */; }; - 4BCF3026199A2F820064207B /* zip_source_commit_write.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF301B199A2F820064207B /* zip_source_commit_write.c */; }; - 4BCF3028199A2F820064207B /* zip_source_rollback_write.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF301C199A2F820064207B /* zip_source_rollback_write.c */; }; - 4BCF302A199A2F820064207B /* zip_source_seek.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF301D199A2F820064207B /* zip_source_seek.c */; }; - 4BCF302C199A2F820064207B /* zip_source_supports.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF301E199A2F820064207B /* zip_source_supports.c */; }; - 4BCF302E199A2F820064207B /* zip_source_tell.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF301F199A2F820064207B /* zip_source_tell.c */; }; - 4BCF3030199A2F820064207B /* zip_source_write.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF3020199A2F820064207B /* zip_source_write.c */; }; - 4BCF3033199ABD3A0064207B /* zip_source_remove.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF3031199ABD3A0064207B /* zip_source_remove.c */; }; - 4BCF3037199ABDDA0064207B /* zip_source_seek_write.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF3034199ABDDA0064207B /* zip_source_seek_write.c */; }; - 4BCF3039199ABDDA0064207B /* zip_source_tell_write.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF3035199ABDDA0064207B /* zip_source_tell_write.c */; }; - 4BD5053419A01BB0007DD28A /* zip_source_call.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD5053219A0116D007DD28A /* zip_source_call.c */; }; - 4BD6CB6419E71CD100710654 /* source_hole.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD6CB5C19E6A5D900710654 /* source_hole.c */; }; - 4BD6CB6619E71CD100710654 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D70815B2F4CF002D5007 /* libz.dylib */; }; - 4BD6CB6719E71CD100710654 /* libzip.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; }; - 4BD6CB6F19E71D6900710654 /* hole.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD6CB5E19E71B3B00710654 /* hole.c */; }; - 4BD7087A1EB1CF73003F351F /* zip_progress.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BD708781EB1CF73003F351F /* zip_progress.c */; }; - 4BE92AA720345E3800509BC8 /* libbz2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BE92AA620345E3800509BC8 /* libbz2.tbd */; }; - 4BE92AAD20346B1900509BC8 /* zip_crypto_openssl.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BE92AAA20346B1900509BC8 /* zip_crypto_openssl.h */; }; - 4BE92AB3203597D700509BC8 /* zip_crypto_commoncrypto.h in Headers */ = {isa = PBXBuildFile; fileRef = 4BE92AB0203597D700509BC8 /* zip_crypto_commoncrypto.h */; }; - 4BE92AB5203597D700509BC8 /* zip_crypto_commoncrypto.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BE92AB1203597D700509BC8 /* zip_crypto_commoncrypto.c */; }; - 4BFF2B551FE13033006EF3F3 /* can_clone_file.c in Sources */ = {isa = PBXBuildFile; fileRef = 4BFF2B541FE13033006EF3F3 /* can_clone_file.c */; }; - 736ED9BB1E3D6B6B00C36873 /* zip_source_winzip_aes_decode.c in Sources */ = {isa = PBXBuildFile; fileRef = 736ED9B91E3D688C00C36873 /* zip_source_winzip_aes_decode.c */; }; - 736ED9BC1E3D6B6F00C36873 /* zip_source_winzip_aes_encode.c in Sources */ = {isa = PBXBuildFile; fileRef = 736ED9BA1E3D688C00C36873 /* zip_source_winzip_aes_encode.c */; }; - 736ED9BF1E3D6B7C00C36873 /* zip_file_set_encryption.c in Sources */ = {isa = PBXBuildFile; fileRef = 736ED9B71E3D688C00C36873 /* zip_file_set_encryption.c */; }; -/* End PBXBuildFile section */ - -/* Begin PBXContainerItemProxy section */ - 3D7E35441B33064500022624 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = libzip; - }; - 4B01D72C15B2F5AC002D5007 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D6FC15B2F4B1002D5007; - remoteInfo = zipmerge; - }; - 4B01D72E15B2F5AC002D5007 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D70A15B2F4EB002D5007; - remoteInfo = zipcmp; - }; - 4B01D73515B2F639002D5007 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4B01D73715B2F643002D5007 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4B2CADAB1C50D57800291DE6 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BACD58815BC2CEA00920691; - remoteInfo = ziptool; - }; - 4B51DDB61FDAE20A00C5CA85 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4B51DDC41FDAE2F000C5CA85 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B51DDB41FDAE20A00C5CA85; - remoteInfo = ziptool_regress; - }; - 4B54447E15C977AF0067BA33 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D72815B2F5A2002D5007; - remoteInfo = "command line tools"; - }; - 4B54448015C977B10067BA33 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BACD5A715BC2D8200920691; - remoteInfo = "test programs"; - }; - 4BACD59615BC2D3800920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4BACD5B715BC2DC900920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4BACD5C615BC2DF200920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4BACD5D515BC2F3700920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4BACD64715BC301300920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4BACD65415BC303B00920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BACD5B515BC2DC900920691; - remoteInfo = add_from_filep; - }; - 4BACD65615BC303B00920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BACD5C415BC2DF200920691; - remoteInfo = fopen_unchanged; - }; - 4BACD65815BC303B00920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BACD5D315BC2F3700920691; - remoteInfo = fread; - }; - 4BACD66815BC303B00920691 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BACD64515BC301300920691; - remoteInfo = tryopen; - }; - 4BCF6A781C3BDDF900F036E9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 3D7E35361B3305FB00022624; - remoteInfo = "in-memory"; - }; - 4BCF6A7A1C3BDDFF00F036E9 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BCF6A681C3BDDD500F036E9; - remoteInfo = examples; - }; - 4BD6CB6119E71CD100710654 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4B01D68A15B2F3F1002D5007; - remoteInfo = "libzip Mac"; - }; - 4BD6CB6D19E71D0800710654 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BD6CB5F19E71CD100710654; - remoteInfo = hole; - }; - 4BFF2B521FE13002006EF3F3 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 4BDC71BF15B181DA00236D3C /* Project object */; - proxyType = 1; - remoteGlobalIDString = 4BFF2B451FE12FCA006EF3F3; - remoteInfo = can_clone_file; - }; -/* End PBXContainerItemProxy section */ - -/* Begin PBXCopyFilesBuildPhase section */ - 3D7E35351B3305FB00022624 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; - 4B01D6FB15B2F4B1002D5007 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; - 4B01D70F15B2F4EB002D5007 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 1; - }; - 4B51DDBC1FDAE20A00C5CA85 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD58715BC2CEA00920691 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5BD15BC2DC900920691 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5CC15BC2DF200920691 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5DB15BC2F3700920691 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD64C15BC301300920691 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BD6CB6819E71CD100710654 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BFF2B4D1FE12FCA006EF3F3 /* CopyFiles */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 12; - dstPath = /usr/share/man/man1/; - dstSubfolderSpec = 0; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 3D77B86517009AA1000A5794 /* extract-version.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "extract-version.sh"; sourceTree = SOURCE_ROOT; }; - 3D7E35371B3305FB00022624 /* in-memory */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "in-memory"; sourceTree = BUILT_PRODUCTS_DIR; }; - 3D7E35401B33063600022624 /* in-memory.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "in-memory.c"; sourceTree = ""; }; - 3D7E35421B33063600022624 /* windows-open.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = "windows-open.c"; sourceTree = ""; }; - 3D7E35471B33076C00022624 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; - 3D9284801C309510001EABA7 /* zip_hash.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_hash.c; sourceTree = ""; usesTabs = 1; }; - 4B00CA21242F59D700E0B71C /* zip_source_pkware_decode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_pkware_decode.c; sourceTree = ""; }; - 4B00CA22242F59D700E0B71C /* zip_pkware.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_pkware.c; sourceTree = ""; }; - 4B00CA23242F59D700E0B71C /* zip_source_pkware_encode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_pkware_encode.c; sourceTree = ""; }; - 4B00CA27242F5C2500E0B71C /* set_file_dostime.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_file_dostime.test; sourceTree = ""; }; - 4B00CA28242F5C2500E0B71C /* cancel_45.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = cancel_45.test; sourceTree = ""; }; - 4B00CA29242F5C2500E0B71C /* set_compression_store_to_xz.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_store_to_xz.test; sourceTree = ""; }; - 4B00CA2A242F5C2500E0B71C /* encryption-nonrandom-pkware.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "encryption-nonrandom-pkware.test"; sourceTree = ""; }; - 4B00CA2B242F5C2500E0B71C /* cancel_90.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = cancel_90.test; sourceTree = ""; }; - 4B00CA2C242F5C2500E0B71C /* set_compression_xz_to_store.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_xz_to_store.test; sourceTree = ""; }; - 4B01D68B15B2F3F1002D5007 /* libzip.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = libzip.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B01D6FD15B2F4B1002D5007 /* zipmerge */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = zipmerge; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B01D70815B2F4CF002D5007 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/usr/lib/libz.dylib; sourceTree = DEVELOPER_DIR; }; - 4B01D71315B2F4EB002D5007 /* zipcmp */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = zipcmp; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B01D72115B2F572002D5007 /* zipcmp.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zipcmp.c; sourceTree = ""; }; - 4B01D72215B2F572002D5007 /* zipmerge.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zipmerge.c; sourceTree = ""; }; - 4B01D73D15B2FB6B002D5007 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; - 4B0454B41E8E3DF7002FA1F9 /* zip_algorithm_bzip2.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_algorithm_bzip2.c; sourceTree = ""; }; - 4B0454B51E8E3DF7002FA1F9 /* zip_algorithm_deflate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_algorithm_deflate.c; sourceTree = ""; }; - 4B0454B61E8E3DF7002FA1F9 /* zip_source_compress.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_compress.c; sourceTree = ""; }; - 4B1ABD1A1A2E5DA700C93867 /* links */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = links; sourceTree = ""; }; - 4B1ABD1B1A2E5E4D00C93867 /* handle_links */ = {isa = PBXFileReference; explicitFileType = text.script.perl; fileEncoding = 4; path = handle_links; sourceTree = ""; }; - 4B1E46E51A08CB7600A376D2 /* zip_error_code_system.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_code_system.mdoc; sourceTree = ""; }; - 4B1E46E61A08CB7600A376D2 /* zip_error_code_zip.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_code_zip.mdoc; sourceTree = ""; }; - 4B1E46E71A08CB7600A376D2 /* zip_error_fini.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_fini.mdoc; sourceTree = ""; }; - 4B1E46E81A08CB7600A376D2 /* zip_error_init.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_init.mdoc; sourceTree = ""; }; - 4B1E46E91A08CB7600A376D2 /* zip_error_set.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_set.mdoc; sourceTree = ""; }; - 4B1E46EA1A08CB7600A376D2 /* zip_error_strerror.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_strerror.mdoc; sourceTree = ""; }; - 4B1E46EB1A08CB7600A376D2 /* zip_error_system_type.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_system_type.mdoc; sourceTree = ""; }; - 4B26FF151A07DF1A000E9788 /* zip_file_get_external_attributes.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_get_external_attributes.mdoc; sourceTree = ""; }; - 4B26FF161A07DF1A000E9788 /* zip_file_set_external_attributes.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_set_external_attributes.mdoc; sourceTree = ""; }; - 4B26FF171A07DF1A000E9788 /* zip_file_set_mtime.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_set_mtime.mdoc; sourceTree = ""; }; - 4B26FF181A07DFEA000E9788 /* mkdocset.pl */ = {isa = PBXFileReference; lastKnownFileType = text.script.perl; path = mkdocset.pl; sourceTree = ""; }; - 4B28A9EC15BACC3900D0C17D /* libzip.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = libzip.mdoc; sourceTree = ""; }; - 4B28A9ED15BACC3900D0C17D /* zip_add_dir.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_add_dir.mdoc; sourceTree = ""; }; - 4B28A9EE15BACC3900D0C17D /* zip_add.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_add.mdoc; sourceTree = ""; }; - 4B28A9EF15BACC3900D0C17D /* zip_close.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_close.mdoc; sourceTree = ""; }; - 4B28A9F015BACC3900D0C17D /* zip_delete.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_delete.mdoc; sourceTree = ""; }; - 4B28A9F115BACC3900D0C17D /* zip_dir_add.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_dir_add.mdoc; sourceTree = ""; }; - 4B28A9F215BACC3900D0C17D /* zip_discard.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_discard.mdoc; sourceTree = ""; }; - 4B28A9F315BACC3900D0C17D /* zip_error_clear.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_clear.mdoc; sourceTree = ""; }; - 4B28A9F415BACC3900D0C17D /* zip_error_get_sys_type.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_get_sys_type.mdoc; sourceTree = ""; }; - 4B28A9F515BACC3900D0C17D /* zip_error_get.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_get.mdoc; sourceTree = ""; }; - 4B28A9F615BACC3900D0C17D /* zip_error_to_str.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_to_str.mdoc; sourceTree = ""; }; - 4B28A9F715BACC3900D0C17D /* zip_errors.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_errors.mdoc; sourceTree = ""; }; - 4B28A9F815BACC3900D0C17D /* zip_fclose.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_fclose.mdoc; sourceTree = ""; }; - 4B28A9F915BACC3900D0C17D /* zip_fdopen.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_fdopen.mdoc; sourceTree = ""; }; - 4B28A9FA15BACC3900D0C17D /* zip_file_add.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_add.mdoc; sourceTree = ""; }; - 4B28A9FB15BACC3900D0C17D /* zip_file_extra_field_delete.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_extra_field_delete.mdoc; sourceTree = ""; }; - 4B28A9FC15BACC3900D0C17D /* zip_file_extra_field_get.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_extra_field_get.mdoc; sourceTree = ""; }; - 4B28A9FD15BACC3900D0C17D /* zip_file_extra_field_set.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_extra_field_set.mdoc; sourceTree = ""; }; - 4B28A9FE15BACC3900D0C17D /* zip_file_extra_fields_count.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_extra_fields_count.mdoc; sourceTree = ""; }; - 4B28A9FF15BACC3900D0C17D /* zip_file_get_comment.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_get_comment.mdoc; sourceTree = ""; }; - 4B28AA0015BACC3900D0C17D /* zip_file_rename.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_rename.mdoc; sourceTree = ""; }; - 4B28AA0115BACC3900D0C17D /* zip_file_set_comment.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_set_comment.mdoc; sourceTree = ""; }; - 4B28AA0215BACC3900D0C17D /* zip_file_strerror.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_strerror.mdoc; sourceTree = ""; }; - 4B28AA0315BACC3900D0C17D /* zip_fopen_encrypted.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_fopen_encrypted.mdoc; sourceTree = ""; }; - 4B28AA0415BACC3900D0C17D /* zip_fopen.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_fopen.mdoc; sourceTree = ""; }; - 4B28AA0515BACC3900D0C17D /* zip_fread.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_fread.mdoc; sourceTree = ""; }; - 4B28AA0615BACC3900D0C17D /* zip_get_archive_comment.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_get_archive_comment.mdoc; sourceTree = ""; }; - 4B28AA0715BACC3900D0C17D /* zip_get_archive_flag.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_get_archive_flag.mdoc; sourceTree = ""; }; - 4B28AA0815BACC3900D0C17D /* zip_get_file_comment.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_get_file_comment.mdoc; sourceTree = ""; }; - 4B28AA0915BACC3900D0C17D /* zip_get_name.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_get_name.mdoc; sourceTree = ""; }; - 4B28AA0A15BACC3900D0C17D /* zip_get_num_entries.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_get_num_entries.mdoc; sourceTree = ""; }; - 4B28AA0B15BACC3900D0C17D /* zip_get_num_files.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_get_num_files.mdoc; sourceTree = ""; }; - 4B28AA0C15BACC3900D0C17D /* zip_name_locate.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_name_locate.mdoc; sourceTree = ""; }; - 4B28AA0D15BACC3900D0C17D /* zip_open.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_open.mdoc; sourceTree = ""; }; - 4B28AA0E15BACC3900D0C17D /* zip_rename.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_rename.mdoc; sourceTree = ""; }; - 4B28AA0F15BACC3900D0C17D /* zip_set_archive_comment.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_set_archive_comment.mdoc; sourceTree = ""; }; - 4B28AA1015BACC3900D0C17D /* zip_set_archive_flag.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_set_archive_flag.mdoc; sourceTree = ""; }; - 4B28AA1115BACC3900D0C17D /* zip_set_default_password.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_set_default_password.mdoc; sourceTree = ""; }; - 4B28AA1215BACC3900D0C17D /* zip_set_file_comment.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_set_file_comment.mdoc; sourceTree = ""; }; - 4B28AA1315BACC3900D0C17D /* zip_set_file_compression.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_set_file_compression.mdoc; sourceTree = ""; }; - 4B28AA1415BACC3900D0C17D /* zip_source_buffer.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_buffer.mdoc; sourceTree = ""; }; - 4B28AA1515BACC3900D0C17D /* zip_source_file.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_file.mdoc; sourceTree = ""; }; - 4B28AA1615BACC3900D0C17D /* zip_source_filep.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_filep.mdoc; sourceTree = ""; }; - 4B28AA1715BACC3900D0C17D /* zip_source_free.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_free.mdoc; sourceTree = ""; }; - 4B28AA1815BACC3900D0C17D /* zip_source_function.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_function.mdoc; sourceTree = ""; }; - 4B28AA1915BACC3900D0C17D /* zip_source_zip.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_zip.mdoc; sourceTree = ""; }; - 4B28AA1A15BACC3900D0C17D /* zip_stat_init.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_stat_init.mdoc; sourceTree = ""; }; - 4B28AA1B15BACC3900D0C17D /* zip_stat.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_stat.mdoc; sourceTree = ""; }; - 4B28AA1C15BACC3900D0C17D /* zip_unchange_all.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_unchange_all.mdoc; sourceTree = ""; }; - 4B28AA1D15BACC3900D0C17D /* zip_unchange_archive.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_unchange_archive.mdoc; sourceTree = ""; }; - 4B28AA1E15BACC3900D0C17D /* zip_unchange.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_unchange.mdoc; sourceTree = ""; }; - 4B28AA1F15BACC3900D0C17D /* zipcmp.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zipcmp.mdoc; sourceTree = ""; }; - 4B28AA2015BACC3900D0C17D /* zipmerge.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zipmerge.mdoc; sourceTree = ""; }; - 4B28AA2215BAD4E200D0C17D /* API-CHANGES.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = "API-CHANGES.md"; sourceTree = ""; }; - 4B28AA2315BAD4E200D0C17D /* AUTHORS */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AUTHORS; sourceTree = ""; }; - 4B28AA2415BAD4E200D0C17D /* NEWS.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = NEWS.md; sourceTree = ""; }; - 4B28AA2515BAD4E200D0C17D /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - 4B28AA2615BAD4E200D0C17D /* THANKS */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = THANKS; sourceTree = ""; }; - 4B28AA2715BAD4E200D0C17D /* TODO.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = TODO.md; sourceTree = ""; }; - 4B3A5F4D1DF96D83005A53A1 /* zip_fseek.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_fseek.c; sourceTree = ""; }; - 4B3A5F4E1DF96D83005A53A1 /* zip_ftell.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_ftell.c; sourceTree = ""; }; - 4B3FAE7F2385C5A300192D6A /* zip_algorithm_xz.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_algorithm_xz.c; sourceTree = ""; }; - 4B3FAE812385C79200192D6A /* liblzma.5.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = liblzma.5.dylib; path = ../../../../../../opt/pkg/lib/liblzma.5.dylib; sourceTree = ""; }; - 4B3FAE832385C9E900192D6A /* README Xcode Project.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = "README Xcode Project.md"; sourceTree = ""; }; - 4B41A2651FE15E99005D8C91 /* clone-fs-add.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "clone-fs-add.test"; sourceTree = ""; }; - 4B41A2661FE15FCE005D8C91 /* clone-fs-delete.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "clone-fs-delete.test"; sourceTree = ""; }; - 4B41A2671FE1604E005D8C91 /* clone-fs-replace.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "clone-fs-replace.test"; sourceTree = ""; }; - 4B4CB5572483D7B7005C5428 /* nihtest.conf.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = nihtest.conf.in; sourceTree = ""; }; - 4B5169A722A7993D00AA4340 /* zip_mkstempm.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_mkstempm.c; sourceTree = ""; }; - 4B51DDB21FDADEDF00C5CA85 /* INSTALL.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = INSTALL.md; sourceTree = ""; }; - 4B51DDB31FDAE1DB00C5CA85 /* ziptool_regress.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ziptool_regress.c; sourceTree = ""; }; - 4B51DDC01FDAE20A00C5CA85 /* ziptool_regress */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ziptool_regress; sourceTree = BUILT_PRODUCTS_DIR; }; - 4B55D93F2475274B00CE8C38 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; - 4B5D0CD4244B154E006C2E93 /* zip_source_get_file_attributes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_get_file_attributes.c; sourceTree = ""; }; - 4B69E6ED2032F1870001EEE7 /* zip_winzip_aes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_winzip_aes.c; sourceTree = ""; }; - 4B69E6F020330D460001EEE7 /* zip_crypto_gnutls.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_crypto_gnutls.c; sourceTree = ""; }; - 4B69E6F2203341D50001EEE7 /* zip_crypto_gnutls.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip_crypto_gnutls.h; sourceTree = ""; }; - 4B69E6F3203342E30001EEE7 /* zip_crypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip_crypto.h; sourceTree = ""; }; - 4B77E61A1FDDCD3A006786BA /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; - 4B77E61B1FDEDEA4006786BA /* clone-buffer-delete.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "clone-buffer-delete.test"; sourceTree = ""; }; - 4B77E61C1FDEDEA5006786BA /* clone-buffer-replace.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "clone-buffer-replace.test"; sourceTree = ""; }; - 4B77E61D1FDEDEA5006786BA /* clone-buffer-add.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "clone-buffer-add.test"; sourceTree = ""; }; - 4B82CED319915F360097BC18 /* zip_file_set_mtime.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_file_set_mtime.c; sourceTree = ""; }; - 4B908F502385BE6C00886355 /* zip_libzip_version.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_libzip_version.c; sourceTree = ""; }; - 4B908F522385BE6D00886355 /* zip_source_accept_empty.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_accept_empty.c; sourceTree = ""; }; - 4B93995524631B3D00AEBDA4 /* zip_source_file_stdio_named.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_file_stdio_named.c; sourceTree = ""; }; - 4B93995624631B3E00AEBDA4 /* zip_source_file_common.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_file_common.c; sourceTree = ""; }; - 4B93995724631B3E00AEBDA4 /* zip_source_file_stdio.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_file_stdio.c; sourceTree = ""; }; - 4B93995824631B3E00AEBDA4 /* zip_source_file_stdio.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zip_source_file_stdio.h; sourceTree = ""; }; - 4B93995924631B3E00AEBDA4 /* zip_source_file.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zip_source_file.h; sourceTree = ""; }; - 4B93995F24640B1700AEBDA4 /* zip_source_file_win32.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip_source_file_win32.h; sourceTree = ""; }; - 4B93996024640B1700AEBDA4 /* zip_source_file_win32.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zip_source_file_win32.c; sourceTree = ""; }; - 4B93996124641D5700AEBDA4 /* zip_source_file_win32_ansi.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zip_source_file_win32_ansi.c; sourceTree = ""; }; - 4B93996224643F5700AEBDA4 /* zip_source_file_win32_utf8.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zip_source_file_win32_utf8.c; sourceTree = ""; }; - 4B9399632464401300AEBDA4 /* zip_source_file_win32_utf16.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_file_win32_utf16.c; sourceTree = ""; }; - 4B93996424644E7E00AEBDA4 /* zip_source_file_win32_named.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zip_source_file_win32_named.c; sourceTree = ""; }; - 4B939965246553FD00AEBDA4 /* appveyor.yml */ = {isa = PBXFileReference; lastKnownFileType = text.yaml; path = appveyor.yml; sourceTree = ""; }; - 4B939966246E842200AEBDA4 /* zip_random_win32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_random_win32.c; sourceTree = ""; }; - 4B939967246E842200AEBDA4 /* zip_random_uwp.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_random_uwp.c; sourceTree = ""; }; - 4B97204D188EBE85002FAFAD /* zip_file_get_external_attributes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_file_get_external_attributes.c; sourceTree = ""; }; - 4B97204E188EBE85002FAFAD /* zip_file_set_external_attributes.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_file_set_external_attributes.c; sourceTree = ""; }; - 4B9E577A24C7026B00CEE0D6 /* zip_algorithm_zstd.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_algorithm_zstd.c; sourceTree = ""; }; - 4B9E578324C9769F00CEE0D6 /* zip_crypto_win.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zip_crypto_win.c; sourceTree = ""; }; - 4B9E578424C9769F00CEE0D6 /* zip_crypto_mbedtls.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zip_crypto_mbedtls.c; sourceTree = ""; }; - 4B9E578524C9769F00CEE0D6 /* zip_crypto_win.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip_crypto_win.h; sourceTree = ""; }; - 4B9E578624C9769F00CEE0D6 /* zip_crypto_mbedtls.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip_crypto_mbedtls.h; sourceTree = ""; }; - 4B9E578724C9770C00CEE0D6 /* libzstd.1.4.5.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libzstd.1.4.5.dylib; path = ../../../../../../usr/local/Cellar/zstd/1.4.5/lib/libzstd.1.4.5.dylib; sourceTree = ""; }; - 4B9E578924C9779900CEE0D6 /* zip_err_str.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_err_str.c; sourceTree = SOURCE_ROOT; }; - 4BACD57715BC2AEF00920691 /* add_from_filep.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = add_from_filep.c; path = ../regress/add_from_filep.c; sourceTree = ""; }; - 4BACD57A15BC2AEF00920691 /* fopen_unchanged.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = fopen_unchanged.c; path = ../regress/fopen_unchanged.c; sourceTree = ""; }; - 4BACD57B15BC2AEF00920691 /* fread.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = fread.c; path = ../regress/fread.c; sourceTree = ""; }; - 4BACD57C15BC2AEF00920691 /* ziptool.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = ziptool.c; sourceTree = ""; }; - 4BACD58415BC2AEF00920691 /* tryopen.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = tryopen.c; path = ../regress/tryopen.c; sourceTree = ""; }; - 4BACD58915BC2CEA00920691 /* ziptool */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ziptool; sourceTree = BUILT_PRODUCTS_DIR; }; - 4BACD5C115BC2DC900920691 /* add_from_filep */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = add_from_filep; sourceTree = BUILT_PRODUCTS_DIR; }; - 4BACD5D015BC2DF200920691 /* fopen_unchanged */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fopen_unchanged; sourceTree = BUILT_PRODUCTS_DIR; }; - 4BACD5DF15BC2F3700920691 /* fread */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = fread; sourceTree = BUILT_PRODUCTS_DIR; }; - 4BACD65015BC301300920691 /* tryopen */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = tryopen; sourceTree = BUILT_PRODUCTS_DIR; }; - 4BC03F781FDD55C1003C7B62 /* zip-in-archive-comment.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "zip-in-archive-comment.test"; sourceTree = ""; }; - 4BC03F791FDD55C1003C7B62 /* decrypt-wrong-password-aes192.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-wrong-password-aes192.test"; sourceTree = ""; }; - 4BC03F7A1FDD55C1003C7B62 /* decrypt-correct-password-pkware.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-correct-password-pkware.test"; sourceTree = ""; }; - 4BC03F7B1FDD55C1003C7B62 /* encryption-nonrandom-aes192.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "encryption-nonrandom-aes192.test"; sourceTree = ""; }; - 4BC03F7C1FDD55C1003C7B62 /* decrypt-wrong-password-pkware.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-wrong-password-pkware.test"; sourceTree = ""; }; - 4BC03F7D1FDD55C1003C7B62 /* preload.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = preload.test; sourceTree = ""; }; - 4BC03F7E1FDD55C1003C7B62 /* open_file_count.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_file_count.test; sourceTree = ""; }; - 4BC03F7F1FDD55C2003C7B62 /* fseek_fail.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fseek_fail.test; sourceTree = ""; }; - 4BC03F801FDD55C2003C7B62 /* open_multidisk.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_multidisk.test; sourceTree = ""; }; - 4BC03F811FDD55C2003C7B62 /* set_compression_deflate_to_bzip2.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_deflate_to_bzip2.test; sourceTree = ""; }; - 4BC03F821FDD55C2003C7B62 /* decrypt-correct-password-aes192.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-correct-password-aes192.test"; sourceTree = ""; }; - 4BC03F831FDD55C2003C7B62 /* encryption-nonrandom-aes256.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "encryption-nonrandom-aes256.test"; sourceTree = ""; }; - 4BC03F841FDD55C2003C7B62 /* fseek_ok.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fseek_ok.test; sourceTree = ""; }; - 4BC03F851FDD55C3003C7B62 /* set_compression_store_to_bzip2.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_store_to_bzip2.test; sourceTree = ""; }; - 4BC03F861FDD55C3003C7B62 /* open_zip64_3mf.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_zip64_3mf.test; sourceTree = ""; }; - 4BC03F871FDD55C3003C7B62 /* fseek_deflated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fseek_deflated.test; sourceTree = ""; }; - 4BC03F881FDD55C3003C7B62 /* encryption-remove.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "encryption-remove.test"; sourceTree = ""; }; - 4BC03F891FDD55C3003C7B62 /* add_from_zip_deflated2.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_zip_deflated2.test; sourceTree = ""; }; - 4BC03F8A1FDD55C3003C7B62 /* decrypt-wrong-password-aes256.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-wrong-password-aes256.test"; sourceTree = ""; }; - 4BC03F8B1FDD55C3003C7B62 /* encryption-nonrandom-aes128.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "encryption-nonrandom-aes128.test"; sourceTree = ""; }; - 4BC03F8C1FDD55C4003C7B62 /* progress.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = progress.test; sourceTree = ""; }; - 4BC03F8D1FDD55C4003C7B62 /* junk_at_start.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = junk_at_start.test; sourceTree = ""; }; - 4BC03F8E1FDD55C4003C7B62 /* buffer-fragment-read.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "buffer-fragment-read.test"; sourceTree = ""; }; - 4BC03F8F1FDD55C4003C7B62 /* decrypt-wrong-password-aes128.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-wrong-password-aes128.test"; sourceTree = ""; }; - 4BC03F901FDD55C4003C7B62 /* fdopen_ok.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fdopen_ok.test; sourceTree = ""; }; - 4BC03F911FDD55C4003C7B62 /* junk_at_end.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = junk_at_end.test; sourceTree = ""; }; - 4BC03F921FDD55C4003C7B62 /* decrypt-correct-password-aes128.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-correct-password-aes128.test"; sourceTree = ""; }; - 4BC03F931FDD55C5003C7B62 /* decrypt-no-password-aes256.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-no-password-aes256.test"; sourceTree = ""; }; - 4BC03F941FDD55C5003C7B62 /* buffer-fragment-write.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "buffer-fragment-write.test"; sourceTree = ""; }; - 4BC03F951FDD55C5003C7B62 /* decrypt-correct-password-aes256.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "decrypt-correct-password-aes256.test"; sourceTree = ""; }; - 4BC03F961FDD55C5003C7B62 /* open_many_fail.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_many_fail.test; sourceTree = ""; }; - 4BC03F971FDD55C5003C7B62 /* set_compression_bzip2_to_deflate.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_bzip2_to_deflate.test; sourceTree = ""; }; - 4BC03F981FDD55C5003C7B62 /* set_file_mtime.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_file_mtime.test; sourceTree = ""; }; - 4BC03F991FDD5617003C7B62 /* nonrandomopen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nonrandomopen.c; sourceTree = ""; }; - 4BC03F9A1FDD5617003C7B62 /* fseek.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = fseek.c; sourceTree = ""; }; - 4BC03F9B1FDD5617003C7B62 /* nonrandomopentest.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = nonrandomopentest.c; sourceTree = ""; }; - 4BC03F9C1FDD5617003C7B62 /* malloc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = malloc.c; sourceTree = ""; }; - 4BC03F9D1FDD5617003C7B62 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; - 4BC03F9E1FDD5642003C7B62 /* extra_field_align.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_field_align.test; sourceTree = ""; }; - 4BC03F9F1FDD5642003C7B62 /* count_entries.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = count_entries.test; sourceTree = ""; }; - 4BC03FA01FDD5660003C7B62 /* cleanup.cmake */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = cleanup.cmake; sourceTree = ""; }; - 4BC03FA11FDD603F003C7B62 /* zip_source_buffer_fragment.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_buffer_fragment.mdoc; sourceTree = ""; }; - 4BC03FA21FDD6B6F003C7B62 /* zip_source_begin_write_cloning.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_begin_write_cloning.c; sourceTree = ""; }; - 4BC3863E1A1BE00E00CDCAAC /* zip_get_error.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_get_error.mdoc; sourceTree = ""; }; - 4BC3863F1A1BE00E00CDCAAC /* zip_source_begin_write.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_begin_write.mdoc; sourceTree = ""; }; - 4BC386401A1BE00E00CDCAAC /* zip_source_close.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_close.mdoc; sourceTree = ""; }; - 4BC386411A1BE00E00CDCAAC /* zip_source_commit_write.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_commit_write.mdoc; sourceTree = ""; }; - 4BC386421A1BE00E00CDCAAC /* zip_source_error.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_error.mdoc; sourceTree = ""; }; - 4BC386431A1BE00E00CDCAAC /* ZIP_SOURCE_GET_ARGS.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = ZIP_SOURCE_GET_ARGS.mdoc; sourceTree = ""; }; - 4BC386441A1BE00E00CDCAAC /* zip_source_is_deleted.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_is_deleted.mdoc; sourceTree = ""; }; - 4BC386451A1BE00E00CDCAAC /* zip_source_keep.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_keep.mdoc; sourceTree = ""; }; - 4BC386461A1BE00E00CDCAAC /* zip_source_make_command_bitmap.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_make_command_bitmap.mdoc; sourceTree = ""; }; - 4BC386471A1BE00E00CDCAAC /* zip_source_open.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_open.mdoc; sourceTree = ""; }; - 4BC386481A1BE00E00CDCAAC /* zip_source_read.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_read.mdoc; sourceTree = ""; }; - 4BC386491A1BE00E00CDCAAC /* zip_source_rollback_write.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_rollback_write.mdoc; sourceTree = ""; }; - 4BC3864A1A1BE00E00CDCAAC /* zip_source_seek_write.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_seek_write.mdoc; sourceTree = ""; }; - 4BC3864B1A1BE00E00CDCAAC /* zip_source_seek.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_seek.mdoc; sourceTree = ""; }; - 4BC3864C1A1BE00E00CDCAAC /* zip_source_stat.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_stat.mdoc; sourceTree = ""; }; - 4BC3864D1A1BE00E00CDCAAC /* zip_source_tell_write.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_tell_write.mdoc; sourceTree = ""; }; - 4BC3864E1A1BE00E00CDCAAC /* zip_source_tell.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_tell.mdoc; sourceTree = ""; }; - 4BC3864F1A1BE00E00CDCAAC /* zip_source_write.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source_write.mdoc; sourceTree = ""; }; - 4BC386501A1BE00E00CDCAAC /* zip_source.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_source.mdoc; sourceTree = ""; }; - 4BC386511A1BE04700CDCAAC /* fix-man-links.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = "fix-man-links.sh"; sourceTree = ""; }; - 4BC386521A1BE04700CDCAAC /* make_zip_errors.sh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = make_zip_errors.sh; sourceTree = ""; }; - 4BC386531A1BE04700CDCAAC /* nih-man.css */ = {isa = PBXFileReference; lastKnownFileType = text.css; path = "nih-man.css"; sourceTree = ""; }; - 4BC972001A0A1D85003A2981 /* zip_error_to_data.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_error_to_data.mdoc; sourceTree = ""; }; - 4BCB434119E9347E0067FAA3 /* zip_buffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_buffer.c; sourceTree = ""; }; - 4BCD77A71A14E404001A9F55 /* zip_file_get_error.mdoc */ = {isa = PBXFileReference; lastKnownFileType = text; path = zip_file_get_error.mdoc; sourceTree = ""; }; - 4BCD77A81A14E921001A9F55 /* zip_source_seek_compute_offset.mdoc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = zip_source_seek_compute_offset.mdoc; sourceTree = ""; }; - 4BCD77A91A14ED5C001A9F55 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; - 4BCF3019199A2F820064207B /* zip_io_util.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_io_util.c; sourceTree = ""; }; - 4BCF301A199A2F820064207B /* zip_source_begin_write.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_begin_write.c; sourceTree = ""; }; - 4BCF301B199A2F820064207B /* zip_source_commit_write.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_commit_write.c; sourceTree = ""; }; - 4BCF301C199A2F820064207B /* zip_source_rollback_write.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_rollback_write.c; sourceTree = ""; }; - 4BCF301D199A2F820064207B /* zip_source_seek.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_seek.c; sourceTree = ""; }; - 4BCF301E199A2F820064207B /* zip_source_supports.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_supports.c; sourceTree = ""; }; - 4BCF301F199A2F820064207B /* zip_source_tell.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_tell.c; sourceTree = ""; }; - 4BCF3020199A2F820064207B /* zip_source_write.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_write.c; sourceTree = ""; }; - 4BCF3031199ABD3A0064207B /* zip_source_remove.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_remove.c; sourceTree = ""; }; - 4BCF3034199ABDDA0064207B /* zip_source_seek_write.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_seek_write.c; sourceTree = ""; }; - 4BCF3035199ABDDA0064207B /* zip_source_tell_write.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_tell_write.c; sourceTree = ""; }; - 4BD155CE191CD28D0046F012 /* NiHTest.pm */ = {isa = PBXFileReference; indentWidth = 4; lastKnownFileType = text.script.perl; path = NiHTest.pm; sourceTree = ""; tabWidth = 4; usesTabs = 1; }; - 4BD155CF191CD28D0046F012 /* runtest.in */ = {isa = PBXFileReference; lastKnownFileType = text; path = runtest.in; sourceTree = ""; }; - 4BD25DA51CF58790005A9EC4 /* compat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = compat.h; sourceTree = ""; }; - 4BD35E411A33366200256CB7 /* add_dir.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_dir.test; sourceTree = ""; }; - 4BD35E421A33366200256CB7 /* add_from_buffer.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_buffer.test; sourceTree = ""; }; - 4BD35E431A33366200256CB7 /* add_from_file_duplicate.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_file_duplicate.test; sourceTree = ""; }; - 4BD35E441A33366200256CB7 /* add_from_file_twice_duplicate.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_file_twice_duplicate.test; sourceTree = ""; }; - 4BD35E451A33366200256CB7 /* add_from_file.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_file.test; sourceTree = ""; }; - 4BD35E461A33366200256CB7 /* add_from_filep.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_filep.test; sourceTree = ""; }; - 4BD35E471A33366200256CB7 /* add_from_stdin.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_stdin.test; sourceTree = ""; }; - 4BD35E481A33366200256CB7 /* add_from_zip_closed.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_zip_closed.test; sourceTree = ""; }; - 4BD35E491A33366200256CB7 /* add_from_zip_deflated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_zip_deflated.test; sourceTree = ""; }; - 4BD35E4A1A33366200256CB7 /* add_from_zip_partial_deflated.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_zip_partial_deflated.test; sourceTree = ""; }; - 4BD35E4B1A33366200256CB7 /* add_from_zip_partial_stored.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_zip_partial_stored.test; sourceTree = ""; }; - 4BD35E4C1A33366200256CB7 /* add_from_zip_stored.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_from_zip_stored.test; sourceTree = ""; }; - 4BD35E4E1A33366200256CB7 /* add_stored_in_memory.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_stored_in_memory.test; sourceTree = ""; }; - 4BD35E4F1A33366200256CB7 /* add_stored.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = add_stored.test; sourceTree = ""; }; - 4BD35E501A33366200256CB7 /* cm-default.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "cm-default.test"; sourceTree = ""; }; - 4BD35E511A33366200256CB7 /* delete_add_same.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = delete_add_same.test; sourceTree = ""; }; - 4BD35E521A33366200256CB7 /* delete_invalid.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = delete_invalid.test; sourceTree = ""; }; - 4BD35E531A33366200256CB7 /* delete_last.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = delete_last.test; sourceTree = ""; }; - 4BD35E541A33366200256CB7 /* delete_multiple_last.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = delete_multiple_last.test; sourceTree = ""; }; - 4BD35E551A33366200256CB7 /* delete_multiple_partial.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = delete_multiple_partial.test; sourceTree = ""; }; - 4BD35E561A33366200256CB7 /* delete_renamed_rename.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = delete_renamed_rename.test; sourceTree = ""; }; - 4BD35E5B1A33366200256CB7 /* encrypt.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = encrypt.test; sourceTree = ""; }; - 4BD35E5C1A33366200256CB7 /* extra_add_multiple.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_add_multiple.test; sourceTree = ""; }; - 4BD35E5D1A33366200256CB7 /* extra_add.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_add.test; sourceTree = ""; }; - 4BD35E5E1A33366200256CB7 /* extra_count_by_id.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_count_by_id.test; sourceTree = ""; }; - 4BD35E5F1A33366200256CB7 /* extra_count_ignore_zip64.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_count_ignore_zip64.test; sourceTree = ""; }; - 4BD35E601A33366200256CB7 /* extra_count.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_count.test; sourceTree = ""; }; - 4BD35E611A33366200256CB7 /* extra_delete_by_id.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_delete_by_id.test; sourceTree = ""; }; - 4BD35E621A33366200256CB7 /* extra_delete.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_delete.test; sourceTree = ""; }; - 4BD35E631A33366200256CB7 /* extra_get_by_id.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_get_by_id.test; sourceTree = ""; }; - 4BD35E641A33366200256CB7 /* extra_get.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_get.test; sourceTree = ""; }; - 4BD35E651A33366200256CB7 /* extra_set_modify_c.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_set_modify_c.test; sourceTree = ""; }; - 4BD35E661A33366200256CB7 /* extra_set_modify_l.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_set_modify_l.test; sourceTree = ""; }; - 4BD35E671A33366200256CB7 /* extra_set.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = extra_set.test; sourceTree = ""; }; - 4BD35E681A33366200256CB7 /* file_comment_encmismatch.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = file_comment_encmismatch.test; sourceTree = ""; }; - 4BD35E691A33366200256CB7 /* fopen_unchanged.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fopen_unchanged.test; sourceTree = ""; }; - 4BD35E6A1A33366200256CB7 /* fread.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = fread.test; sourceTree = ""; }; - 4BD35E6B1A33366200256CB7 /* get_comment.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = get_comment.test; sourceTree = ""; }; - 4BD35E8E1A33366200256CB7 /* name_locate.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = name_locate.test; sourceTree = ""; }; - 4BD35E8F1A33366200256CB7 /* open_cons_extrabytes.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_cons_extrabytes.test; sourceTree = ""; }; - 4BD35E901A33366200256CB7 /* open_empty_2.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_empty_2.test; sourceTree = ""; }; - 4BD35E911A33366200256CB7 /* open_empty.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_empty.test; sourceTree = ""; }; - 4BD35E921A33366200256CB7 /* open_extrabytes.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_extrabytes.test; sourceTree = ""; }; - 4BD35E931A33366200256CB7 /* open_filename_duplicate_consistency.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_filename_duplicate_consistency.test; sourceTree = ""; }; - 4BD35E941A33366200256CB7 /* open_filename_duplicate_empty_consistency.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_filename_duplicate_empty_consistency.test; sourceTree = ""; }; - 4BD35E951A33366200256CB7 /* open_filename_duplicate_empty.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_filename_duplicate_empty.test; sourceTree = ""; }; - 4BD35E961A33366200256CB7 /* open_filename_duplicate.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_filename_duplicate.test; sourceTree = ""; }; - 4BD35E971A33366200256CB7 /* open_filename_empty.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_filename_empty.test; sourceTree = ""; }; - 4BD35E981A33366200256CB7 /* open_incons.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_incons.test; sourceTree = ""; }; - 4BD35E991A33366200256CB7 /* open_many_ok.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_many_ok.test; sourceTree = ""; }; - 4BD35E9A1A33366200256CB7 /* open_new_but_exists.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_new_but_exists.test; sourceTree = ""; }; - 4BD35E9B1A33366200256CB7 /* open_new_ok.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_new_ok.test; sourceTree = ""; }; - 4BD35E9C1A33366200256CB7 /* open_nonarchive.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_nonarchive.test; sourceTree = ""; }; - 4BD35E9D1A33366200256CB7 /* open_nosuchfile.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_nosuchfile.test; sourceTree = ""; }; - 4BD35E9E1A33366200256CB7 /* open_ok.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_ok.test; sourceTree = ""; }; - 4BD35E9F1A33366200256CB7 /* open_too_short.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_too_short.test; sourceTree = ""; }; - 4BD35EA01A33366200256CB7 /* open_truncate.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_truncate.test; sourceTree = ""; }; - 4BD35EA11A33366200256CB7 /* open_zip64_ok.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = open_zip64_ok.test; sourceTree = ""; }; - 4BD35EA21A33366200256CB7 /* rename_ascii.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rename_ascii.test; sourceTree = ""; }; - 4BD35EA31A33366200256CB7 /* rename_cp437.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rename_cp437.test; sourceTree = ""; }; - 4BD35EA41A33366200256CB7 /* rename_deleted.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rename_deleted.test; sourceTree = ""; }; - 4BD35EA51A33366200256CB7 /* rename_fail.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rename_fail.test; sourceTree = ""; }; - 4BD35EA61A33366200256CB7 /* rename_ok.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rename_ok.test; sourceTree = ""; }; - 4BD35EA81A33366200256CB7 /* rename_utf8_encmismatch.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rename_utf8_encmismatch.test; sourceTree = ""; }; - 4BD35EA91A33366200256CB7 /* rename_utf8.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = rename_utf8.test; sourceTree = ""; }; - 4BD35EAA1A33366200256CB7 /* set_comment_all.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_comment_all.test; sourceTree = ""; }; - 4BD35EAB1A33366200256CB7 /* set_comment_localonly.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_comment_localonly.test; sourceTree = ""; }; - 4BD35EAC1A33366200256CB7 /* set_comment_removeglobal.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_comment_removeglobal.test; sourceTree = ""; }; - 4BD35EAD1A33366200256CB7 /* set_comment_revert.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_comment_revert.test; sourceTree = ""; }; - 4BD35EAE1A33366200256CB7 /* set_compression_deflate_to_deflate.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_deflate_to_deflate.test; sourceTree = ""; }; - 4BD35EAF1A33366200256CB7 /* set_compression_deflate_to_store.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_deflate_to_store.test; sourceTree = ""; }; - 4BD35EB01A33366200256CB7 /* set_compression_store_to_deflate.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_store_to_deflate.test; sourceTree = ""; }; - 4BD35EB11A33366200256CB7 /* set_compression_store_to_store.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_store_to_store.test; sourceTree = ""; }; - 4BD35EB21A33366200256CB7 /* set_compression_unknown.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = set_compression_unknown.test; sourceTree = ""; }; - 4BD35EB41A33366200256CB7 /* stat_index_cp437_guess.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_cp437_guess.test; sourceTree = ""; }; - 4BD35EB51A33366200256CB7 /* stat_index_cp437_raw.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_cp437_raw.test; sourceTree = ""; }; - 4BD35EB61A33366200256CB7 /* stat_index_cp437_strict.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_cp437_strict.test; sourceTree = ""; }; - 4BD35EB71A33366200256CB7 /* stat_index_fileorder.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_fileorder.test; sourceTree = ""; }; - 4BD35EB81A33366200256CB7 /* stat_index_streamed_zip64.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_streamed_zip64.test; sourceTree = ""; }; - 4BD35EB91A33366200256CB7 /* stat_index_streamed.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_streamed.test; sourceTree = ""; }; - 4BD35EBA1A33366200256CB7 /* stat_index_utf8_guess.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_utf8_guess.test; sourceTree = ""; }; - 4BD35EBB1A33366200256CB7 /* stat_index_utf8_raw.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_utf8_raw.test; sourceTree = ""; }; - 4BD35EBC1A33366200256CB7 /* stat_index_utf8_strict.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_utf8_strict.test; sourceTree = ""; }; - 4BD35EBD1A33366200256CB7 /* stat_index_utf8_unmarked_strict.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_utf8_unmarked_strict.test; sourceTree = ""; }; - 4BD35EBE1A33366200256CB7 /* stat_index_zip64.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = stat_index_zip64.test; sourceTree = ""; }; - 4BD35EE11A33366300256CB7 /* utf-8-standardization.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "utf-8-standardization.test"; sourceTree = ""; }; - 4BD35EE31A33366300256CB7 /* zip64_creation.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = zip64_creation.test; sourceTree = ""; }; - 4BD35EE41A33366300256CB7 /* zip64_stored_creation.test */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = zip64_stored_creation.test; sourceTree = ""; }; - 4BD5053219A0116D007DD28A /* zip_source_call.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_call.c; sourceTree = ""; }; - 4BD6CB5C19E6A5D900710654 /* source_hole.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = source_hole.c; sourceTree = ""; }; - 4BD6CB5E19E71B3B00710654 /* hole.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = hole.c; sourceTree = ""; }; - 4BD6CB6C19E71CD100710654 /* hole */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = hole; sourceTree = BUILT_PRODUCTS_DIR; }; - 4BD708781EB1CF73003F351F /* zip_progress.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_progress.c; sourceTree = ""; }; - 4BDC71F115B1B25E00236D3C /* zip_add_dir.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_add_dir.c; path = ../lib/zip_add_dir.c; sourceTree = ""; }; - 4BDC71F215B1B25E00236D3C /* zip_add_entry.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_add_entry.c; path = ../lib/zip_add_entry.c; sourceTree = ""; }; - 4BDC71F315B1B25E00236D3C /* zip_add.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_add.c; path = ../lib/zip_add.c; sourceTree = ""; }; - 4BDC71F415B1B25E00236D3C /* zip_close.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_close.c; path = ../lib/zip_close.c; sourceTree = ""; }; - 4BDC71F515B1B25E00236D3C /* zip_delete.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_delete.c; path = ../lib/zip_delete.c; sourceTree = ""; }; - 4BDC71F615B1B25E00236D3C /* zip_dir_add.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_dir_add.c; path = ../lib/zip_dir_add.c; sourceTree = ""; }; - 4BDC71F715B1B25E00236D3C /* zip_dirent.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_dirent.c; path = ../lib/zip_dirent.c; sourceTree = ""; }; - 4BDC71F815B1B25E00236D3C /* zip_discard.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_discard.c; path = ../lib/zip_discard.c; sourceTree = ""; }; - 4BDC71F915B1B25E00236D3C /* zip_entry.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_entry.c; path = ../lib/zip_entry.c; sourceTree = ""; }; - 4BDC71FB15B1B25E00236D3C /* zip_error_clear.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_error_clear.c; path = ../lib/zip_error_clear.c; sourceTree = ""; }; - 4BDC71FC15B1B25E00236D3C /* zip_error_get_sys_type.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_error_get_sys_type.c; path = ../lib/zip_error_get_sys_type.c; sourceTree = ""; }; - 4BDC71FD15B1B25E00236D3C /* zip_error_get.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_error_get.c; path = ../lib/zip_error_get.c; sourceTree = ""; }; - 4BDC71FE15B1B25E00236D3C /* zip_error_strerror.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_error_strerror.c; path = ../lib/zip_error_strerror.c; sourceTree = ""; }; - 4BDC71FF15B1B25E00236D3C /* zip_error_to_str.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_error_to_str.c; path = ../lib/zip_error_to_str.c; sourceTree = ""; }; - 4BDC720015B1B25E00236D3C /* zip_error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_error.c; path = ../lib/zip_error.c; sourceTree = ""; }; - 4BDC720115B1B25E00236D3C /* zip_extra_field_api.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_extra_field_api.c; path = ../lib/zip_extra_field_api.c; sourceTree = ""; }; - 4BDC720215B1B25E00236D3C /* zip_extra_field.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_extra_field.c; path = ../lib/zip_extra_field.c; sourceTree = ""; }; - 4BDC720315B1B25E00236D3C /* zip_fclose.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_fclose.c; path = ../lib/zip_fclose.c; sourceTree = ""; }; - 4BDC720415B1B25E00236D3C /* zip_fdopen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_fdopen.c; path = ../lib/zip_fdopen.c; sourceTree = ""; }; - 4BDC720515B1B25E00236D3C /* zip_file_add.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_add.c; path = ../lib/zip_file_add.c; sourceTree = ""; }; - 4BDC720615B1B25E00236D3C /* zip_file_error_clear.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_error_clear.c; path = ../lib/zip_file_error_clear.c; sourceTree = ""; }; - 4BDC720715B1B25E00236D3C /* zip_file_error_get.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_error_get.c; path = ../lib/zip_file_error_get.c; sourceTree = ""; }; - 4BDC720815B1B25E00236D3C /* zip_file_get_comment.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_get_comment.c; path = ../lib/zip_file_get_comment.c; sourceTree = ""; }; - 4BDC720915B1B25E00236D3C /* zip_file_get_offset.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_get_offset.c; path = ../lib/zip_file_get_offset.c; sourceTree = ""; }; - 4BDC720A15B1B25E00236D3C /* zip_file_rename.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_rename.c; path = ../lib/zip_file_rename.c; sourceTree = ""; }; - 4BDC720B15B1B25E00236D3C /* zip_file_replace.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_replace.c; path = ../lib/zip_file_replace.c; sourceTree = ""; }; - 4BDC720C15B1B25E00236D3C /* zip_file_set_comment.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_set_comment.c; path = ../lib/zip_file_set_comment.c; sourceTree = ""; }; - 4BDC720D15B1B25E00236D3C /* zip_file_strerror.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_file_strerror.c; path = ../lib/zip_file_strerror.c; sourceTree = ""; }; - 4BDC720F15B1B25E00236D3C /* zip_fopen_encrypted.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_fopen_encrypted.c; path = ../lib/zip_fopen_encrypted.c; sourceTree = ""; }; - 4BDC721015B1B25E00236D3C /* zip_fopen_index_encrypted.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_fopen_index_encrypted.c; path = ../lib/zip_fopen_index_encrypted.c; sourceTree = ""; }; - 4BDC721115B1B25E00236D3C /* zip_fopen_index.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_fopen_index.c; path = ../lib/zip_fopen_index.c; sourceTree = ""; }; - 4BDC721215B1B25E00236D3C /* zip_fopen.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_fopen.c; path = ../lib/zip_fopen.c; sourceTree = ""; }; - 4BDC721315B1B25E00236D3C /* zip_fread.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_fread.c; path = ../lib/zip_fread.c; sourceTree = ""; }; - 4BDC721415B1B25E00236D3C /* zip_get_archive_comment.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_get_archive_comment.c; path = ../lib/zip_get_archive_comment.c; sourceTree = ""; }; - 4BDC721515B1B25E00236D3C /* zip_get_archive_flag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_get_archive_flag.c; path = ../lib/zip_get_archive_flag.c; sourceTree = ""; }; - 4BDC721715B1B25E00236D3C /* zip_get_encryption_implementation.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_get_encryption_implementation.c; path = ../lib/zip_get_encryption_implementation.c; sourceTree = ""; }; - 4BDC721815B1B25E00236D3C /* zip_get_file_comment.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_get_file_comment.c; path = ../lib/zip_get_file_comment.c; sourceTree = ""; }; - 4BDC721915B1B25E00236D3C /* zip_get_name.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_get_name.c; path = ../lib/zip_get_name.c; sourceTree = ""; }; - 4BDC721A15B1B25E00236D3C /* zip_get_num_entries.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_get_num_entries.c; path = ../lib/zip_get_num_entries.c; sourceTree = ""; }; - 4BDC721B15B1B25E00236D3C /* zip_get_num_files.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_get_num_files.c; path = ../lib/zip_get_num_files.c; sourceTree = ""; }; - 4BDC721C15B1B25E00236D3C /* zip_memdup.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_memdup.c; path = ../lib/zip_memdup.c; sourceTree = ""; }; - 4BDC721D15B1B25E00236D3C /* zip_name_locate.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_name_locate.c; path = ../lib/zip_name_locate.c; sourceTree = ""; }; - 4BDC721E15B1B25E00236D3C /* zip_new.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_new.c; path = ../lib/zip_new.c; sourceTree = ""; }; - 4BDC721F15B1B25E00236D3C /* zip_open.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_open.c; path = ../lib/zip_open.c; sourceTree = ""; tabWidth = 8; usesTabs = 1; }; - 4BDC722015B1B25E00236D3C /* zip_rename.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_rename.c; path = ../lib/zip_rename.c; sourceTree = ""; }; - 4BDC722115B1B25E00236D3C /* zip_replace.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_replace.c; path = ../lib/zip_replace.c; sourceTree = ""; }; - 4BDC722215B1B25E00236D3C /* zip_set_archive_comment.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_set_archive_comment.c; path = ../lib/zip_set_archive_comment.c; sourceTree = ""; }; - 4BDC722315B1B25E00236D3C /* zip_set_archive_flag.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_set_archive_flag.c; path = ../lib/zip_set_archive_flag.c; sourceTree = ""; }; - 4BDC722415B1B25E00236D3C /* zip_set_default_password.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_set_default_password.c; path = ../lib/zip_set_default_password.c; sourceTree = ""; }; - 4BDC722515B1B25E00236D3C /* zip_set_file_comment.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_set_file_comment.c; path = ../lib/zip_set_file_comment.c; sourceTree = ""; }; - 4BDC722615B1B25E00236D3C /* zip_set_file_compression.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_set_file_compression.c; path = ../lib/zip_set_file_compression.c; sourceTree = ""; }; - 4BDC722715B1B25E00236D3C /* zip_set_name.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_set_name.c; path = ../lib/zip_set_name.c; sourceTree = ""; }; - 4BDC722815B1B25E00236D3C /* zip_source_buffer.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_buffer.c; path = ../lib/zip_source_buffer.c; sourceTree = ""; }; - 4BDC722915B1B25E00236D3C /* zip_source_close.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_close.c; path = ../lib/zip_source_close.c; sourceTree = ""; }; - 4BDC722A15B1B25E00236D3C /* zip_source_crc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_crc.c; path = ../lib/zip_source_crc.c; sourceTree = ""; }; - 4BDC722C15B1B25E00236D3C /* zip_source_error.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_error.c; path = ../lib/zip_source_error.c; sourceTree = ""; }; - 4BDC722F15B1B25E00236D3C /* zip_source_free.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_free.c; path = ../lib/zip_source_free.c; sourceTree = ""; }; - 4BDC723015B1B25E00236D3C /* zip_source_function.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_function.c; path = ../lib/zip_source_function.c; sourceTree = ""; }; - 4BDC723115B1B25E00236D3C /* zip_source_layered.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_layered.c; path = ../lib/zip_source_layered.c; sourceTree = ""; }; - 4BDC723215B1B25E00236D3C /* zip_source_open.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_open.c; path = ../lib/zip_source_open.c; sourceTree = ""; }; - 4BDC723515B1B25E00236D3C /* zip_source_read.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_read.c; path = ../lib/zip_source_read.c; sourceTree = ""; }; - 4BDC723615B1B25E00236D3C /* zip_source_stat.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_stat.c; path = ../lib/zip_source_stat.c; sourceTree = ""; }; - 4BDC723715B1B25E00236D3C /* zip_source_window.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_window.c; path = ../lib/zip_source_window.c; sourceTree = ""; }; - 4BDC723815B1B25E00236D3C /* zip_source_zip_new.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_zip_new.c; path = ../lib/zip_source_zip_new.c; sourceTree = ""; }; - 4BDC723915B1B25E00236D3C /* zip_source_zip.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_source_zip.c; path = ../lib/zip_source_zip.c; sourceTree = ""; }; - 4BDC723A15B1B25E00236D3C /* zip_stat_index.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_stat_index.c; path = ../lib/zip_stat_index.c; sourceTree = ""; }; - 4BDC723B15B1B25E00236D3C /* zip_stat_init.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_stat_init.c; path = ../lib/zip_stat_init.c; sourceTree = ""; }; - 4BDC723C15B1B25E00236D3C /* zip_stat.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_stat.c; path = ../lib/zip_stat.c; sourceTree = ""; }; - 4BDC723D15B1B25E00236D3C /* zip_strerror.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_strerror.c; path = ../lib/zip_strerror.c; sourceTree = ""; }; - 4BDC723E15B1B25E00236D3C /* zip_string.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_string.c; path = ../lib/zip_string.c; sourceTree = ""; }; - 4BDC723F15B1B25E00236D3C /* zip_unchange_all.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_unchange_all.c; path = ../lib/zip_unchange_all.c; sourceTree = ""; }; - 4BDC724015B1B25E00236D3C /* zip_unchange_archive.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_unchange_archive.c; path = ../lib/zip_unchange_archive.c; sourceTree = ""; }; - 4BDC724115B1B25E00236D3C /* zip_unchange_data.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_unchange_data.c; path = ../lib/zip_unchange_data.c; sourceTree = ""; }; - 4BDC724215B1B25E00236D3C /* zip_unchange.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zip_unchange.c; path = ../lib/zip_unchange.c; sourceTree = ""; }; - 4BDC724315B1B25E00236D3C /* zip_utf-8.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = "zip_utf-8.c"; path = "../lib/zip_utf-8.c"; sourceTree = ""; }; - 4BDC729815B1B2A600236D3C /* zip.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zip.h; path = ../lib/zip.h; sourceTree = ""; }; - 4BDC729915B1B2A600236D3C /* zipint.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zipint.h; path = ../lib/zipint.h; sourceTree = ""; }; - 4BDC729E15B1B4E900236D3C /* zipconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = zipconf.h; sourceTree = SOURCE_ROOT; }; - 4BDC72A015B1B56400236D3C /* config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = SOURCE_ROOT; }; - 4BE402AC19D94AE400298248 /* zip_source_is_deleted.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_is_deleted.c; sourceTree = ""; }; - 4BE92AA420345E2E00509BC8 /* libgnutls.30.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libgnutls.30.dylib; path = ../../../../../../opt/pkg/lib/libgnutls.30.dylib; sourceTree = ""; }; - 4BE92AA620345E3800509BC8 /* libbz2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libbz2.tbd; path = usr/lib/libbz2.tbd; sourceTree = SDKROOT; }; - 4BE92AA820345E5500509BC8 /* libnettle.6.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libnettle.6.dylib; path = ../../../../../../opt/pkg/lib/libnettle.6.dylib; sourceTree = ""; }; - 4BE92AAA20346B1900509BC8 /* zip_crypto_openssl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip_crypto_openssl.h; sourceTree = ""; }; - 4BE92AAB20346B1900509BC8 /* zip_crypto_openssl.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zip_crypto_openssl.c; sourceTree = ""; }; - 4BE92AB0203597D700509BC8 /* zip_crypto_commoncrypto.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip_crypto_commoncrypto.h; sourceTree = ""; }; - 4BE92AB1203597D700509BC8 /* zip_crypto_commoncrypto.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = zip_crypto_commoncrypto.c; sourceTree = ""; }; - 4BFF2B341FDEEF8B006EF3F3 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; - 4BFF2B351FDEF277006EF3F3 /* cmake-config.h.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "cmake-config.h.in"; sourceTree = ""; }; - 4BFF2B361FDEF277006EF3F3 /* cmake-zipconf.h.in */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "cmake-zipconf.h.in"; sourceTree = ""; }; - 4BFF2B511FE12FCA006EF3F3 /* can_clone_file */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = can_clone_file; sourceTree = BUILT_PRODUCTS_DIR; }; - 4BFF2B541FE13033006EF3F3 /* can_clone_file.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = can_clone_file.c; sourceTree = ""; }; - 736ED9B71E3D688C00C36873 /* zip_file_set_encryption.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_file_set_encryption.c; sourceTree = ""; }; - 736ED9B81E3D688C00C36873 /* zip_random_unix.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_random_unix.c; sourceTree = ""; }; - 736ED9B91E3D688C00C36873 /* zip_source_winzip_aes_decode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_winzip_aes_decode.c; sourceTree = ""; }; - 736ED9BA1E3D688C00C36873 /* zip_source_winzip_aes_encode.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = zip_source_winzip_aes_encode.c; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - 3D7E35341B3305FB00022624 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 3D7E35481B33076C00022624 /* libz.dylib in Frameworks */, - 3D7E35461B33064B00022624 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B01D68715B2F3F1002D5007 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B9E578824C9770C00CEE0D6 /* libzstd.1.4.5.dylib in Frameworks */, - 4BE92AA720345E3800509BC8 /* libbz2.tbd in Frameworks */, - 4B01D73C15B2F6AF002D5007 /* libz.dylib in Frameworks */, - 4B3FAE822385C79200192D6A /* liblzma.5.dylib in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B01D6FA15B2F4B1002D5007 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B01D70915B2F4CF002D5007 /* libz.dylib in Frameworks */, - 4B01D70715B2F4C5002D5007 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B01D70C15B2F4EB002D5007 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B01D70D15B2F4EB002D5007 /* libz.dylib in Frameworks */, - 4B01D70E15B2F4EB002D5007 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B51DDB91FDAE20A00C5CA85 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B51DDBA1FDAE20A00C5CA85 /* libz.dylib in Frameworks */, - 4B51DDBB1FDAE20A00C5CA85 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD58615BC2CEA00920691 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD59415BC2D0800920691 /* libz.dylib in Frameworks */, - 4BACD59315BC2CFA00920691 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5BA15BC2DC900920691 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD5BB15BC2DC900920691 /* libz.dylib in Frameworks */, - 4BACD5BC15BC2DC900920691 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5C915BC2DF200920691 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD5CA15BC2DF200920691 /* libz.dylib in Frameworks */, - 4BACD5CB15BC2DF200920691 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5D815BC2F3700920691 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD5D915BC2F3700920691 /* libz.dylib in Frameworks */, - 4BACD5DA15BC2F3700920691 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD64915BC301300920691 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD64A15BC301300920691 /* libz.dylib in Frameworks */, - 4BACD64B15BC301300920691 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BD6CB6519E71CD100710654 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BD6CB6619E71CD100710654 /* libz.dylib in Frameworks */, - 4BD6CB6719E71CD100710654 /* libzip.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BFF2B4A1FE12FCA006EF3F3 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXFrameworksBuildPhase section */ - -/* Begin PBXGroup section */ - 3D7E353E1B33063600022624 /* examples */ = { - isa = PBXGroup; - children = ( - 3D7E35401B33063600022624 /* in-memory.c */, - 3D7E35421B33063600022624 /* windows-open.c */, - ); - name = examples; - path = ../examples; - sourceTree = ""; - }; - 4B01D72015B2F54C002D5007 /* src */ = { - isa = PBXGroup; - children = ( - 4B55D93F2475274B00CE8C38 /* CMakeLists.txt */, - 4B01D72115B2F572002D5007 /* zipcmp.c */, - 4B01D72215B2F572002D5007 /* zipmerge.c */, - 4BACD57C15BC2AEF00920691 /* ziptool.c */, - ); - name = src; - path = ../src; - sourceTree = ""; - }; - 4B28A9EA15BACBE100D0C17D /* man */ = { - isa = PBXGroup; - children = ( - 4BCD77A91A14ED5C001A9F55 /* CMakeLists.txt */, - 4B1ABD1B1A2E5E4D00C93867 /* handle_links */, - 4B1ABD1A1A2E5DA700C93867 /* links */, - 4BC386511A1BE04700CDCAAC /* fix-man-links.sh */, - 4BC386521A1BE04700CDCAAC /* make_zip_errors.sh */, - 4B26FF181A07DFEA000E9788 /* mkdocset.pl */, - 4BC386531A1BE04700CDCAAC /* nih-man.css */, - 4B28A9EC15BACC3900D0C17D /* libzip.mdoc */, - 4B28A9ED15BACC3900D0C17D /* zip_add_dir.mdoc */, - 4B28A9EE15BACC3900D0C17D /* zip_add.mdoc */, - 4B28A9EF15BACC3900D0C17D /* zip_close.mdoc */, - 4B28A9F015BACC3900D0C17D /* zip_delete.mdoc */, - 4B28A9F115BACC3900D0C17D /* zip_dir_add.mdoc */, - 4B28A9F215BACC3900D0C17D /* zip_discard.mdoc */, - 4B28A9F315BACC3900D0C17D /* zip_error_clear.mdoc */, - 4B1E46E51A08CB7600A376D2 /* zip_error_code_system.mdoc */, - 4B1E46E61A08CB7600A376D2 /* zip_error_code_zip.mdoc */, - 4B1E46E71A08CB7600A376D2 /* zip_error_fini.mdoc */, - 4B28A9F415BACC3900D0C17D /* zip_error_get_sys_type.mdoc */, - 4B28A9F515BACC3900D0C17D /* zip_error_get.mdoc */, - 4B1E46E81A08CB7600A376D2 /* zip_error_init.mdoc */, - 4B1E46E91A08CB7600A376D2 /* zip_error_set.mdoc */, - 4B1E46EA1A08CB7600A376D2 /* zip_error_strerror.mdoc */, - 4B1E46EB1A08CB7600A376D2 /* zip_error_system_type.mdoc */, - 4BC972001A0A1D85003A2981 /* zip_error_to_data.mdoc */, - 4B28A9F615BACC3900D0C17D /* zip_error_to_str.mdoc */, - 4B28A9F715BACC3900D0C17D /* zip_errors.mdoc */, - 4B28A9F815BACC3900D0C17D /* zip_fclose.mdoc */, - 4B28A9F915BACC3900D0C17D /* zip_fdopen.mdoc */, - 4B28A9FA15BACC3900D0C17D /* zip_file_add.mdoc */, - 4B28A9FB15BACC3900D0C17D /* zip_file_extra_field_delete.mdoc */, - 4B28A9FC15BACC3900D0C17D /* zip_file_extra_field_get.mdoc */, - 4B28A9FD15BACC3900D0C17D /* zip_file_extra_field_set.mdoc */, - 4B28A9FE15BACC3900D0C17D /* zip_file_extra_fields_count.mdoc */, - 4B28A9FF15BACC3900D0C17D /* zip_file_get_comment.mdoc */, - 4BCD77A71A14E404001A9F55 /* zip_file_get_error.mdoc */, - 4B26FF151A07DF1A000E9788 /* zip_file_get_external_attributes.mdoc */, - 4B28AA0015BACC3900D0C17D /* zip_file_rename.mdoc */, - 4B28AA0115BACC3900D0C17D /* zip_file_set_comment.mdoc */, - 4B26FF161A07DF1A000E9788 /* zip_file_set_external_attributes.mdoc */, - 4B26FF171A07DF1A000E9788 /* zip_file_set_mtime.mdoc */, - 4B28AA0215BACC3900D0C17D /* zip_file_strerror.mdoc */, - 4B28AA0315BACC3900D0C17D /* zip_fopen_encrypted.mdoc */, - 4B28AA0415BACC3900D0C17D /* zip_fopen.mdoc */, - 4B28AA0515BACC3900D0C17D /* zip_fread.mdoc */, - 4B28AA0615BACC3900D0C17D /* zip_get_archive_comment.mdoc */, - 4B28AA0715BACC3900D0C17D /* zip_get_archive_flag.mdoc */, - 4BC3863E1A1BE00E00CDCAAC /* zip_get_error.mdoc */, - 4B28AA0815BACC3900D0C17D /* zip_get_file_comment.mdoc */, - 4B28AA0915BACC3900D0C17D /* zip_get_name.mdoc */, - 4B28AA0A15BACC3900D0C17D /* zip_get_num_entries.mdoc */, - 4B28AA0B15BACC3900D0C17D /* zip_get_num_files.mdoc */, - 4B28AA0C15BACC3900D0C17D /* zip_name_locate.mdoc */, - 4B28AA0D15BACC3900D0C17D /* zip_open.mdoc */, - 4B28AA0E15BACC3900D0C17D /* zip_rename.mdoc */, - 4B28AA0F15BACC3900D0C17D /* zip_set_archive_comment.mdoc */, - 4B28AA1015BACC3900D0C17D /* zip_set_archive_flag.mdoc */, - 4B28AA1115BACC3900D0C17D /* zip_set_default_password.mdoc */, - 4B28AA1215BACC3900D0C17D /* zip_set_file_comment.mdoc */, - 4B28AA1315BACC3900D0C17D /* zip_set_file_compression.mdoc */, - 4BC3863F1A1BE00E00CDCAAC /* zip_source_begin_write.mdoc */, - 4B28AA1415BACC3900D0C17D /* zip_source_buffer.mdoc */, - 4BC03FA11FDD603F003C7B62 /* zip_source_buffer_fragment.mdoc */, - 4BC386401A1BE00E00CDCAAC /* zip_source_close.mdoc */, - 4BC386411A1BE00E00CDCAAC /* zip_source_commit_write.mdoc */, - 4BC386421A1BE00E00CDCAAC /* zip_source_error.mdoc */, - 4B28AA1515BACC3900D0C17D /* zip_source_file.mdoc */, - 4B28AA1615BACC3900D0C17D /* zip_source_filep.mdoc */, - 4B28AA1715BACC3900D0C17D /* zip_source_free.mdoc */, - 4B28AA1815BACC3900D0C17D /* zip_source_function.mdoc */, - 4BC386431A1BE00E00CDCAAC /* ZIP_SOURCE_GET_ARGS.mdoc */, - 4BC386441A1BE00E00CDCAAC /* zip_source_is_deleted.mdoc */, - 4BC386451A1BE00E00CDCAAC /* zip_source_keep.mdoc */, - 4BC386461A1BE00E00CDCAAC /* zip_source_make_command_bitmap.mdoc */, - 4BC386471A1BE00E00CDCAAC /* zip_source_open.mdoc */, - 4BC386481A1BE00E00CDCAAC /* zip_source_read.mdoc */, - 4BC386491A1BE00E00CDCAAC /* zip_source_rollback_write.mdoc */, - 4BCD77A81A14E921001A9F55 /* zip_source_seek_compute_offset.mdoc */, - 4BC3864A1A1BE00E00CDCAAC /* zip_source_seek_write.mdoc */, - 4BC3864B1A1BE00E00CDCAAC /* zip_source_seek.mdoc */, - 4BC3864C1A1BE00E00CDCAAC /* zip_source_stat.mdoc */, - 4BC3864D1A1BE00E00CDCAAC /* zip_source_tell_write.mdoc */, - 4BC3864E1A1BE00E00CDCAAC /* zip_source_tell.mdoc */, - 4BC3864F1A1BE00E00CDCAAC /* zip_source_write.mdoc */, - 4B28AA1915BACC3900D0C17D /* zip_source_zip.mdoc */, - 4BC386501A1BE00E00CDCAAC /* zip_source.mdoc */, - 4B28AA1A15BACC3900D0C17D /* zip_stat_init.mdoc */, - 4B28AA1B15BACC3900D0C17D /* zip_stat.mdoc */, - 4B28AA1C15BACC3900D0C17D /* zip_unchange_all.mdoc */, - 4B28AA1D15BACC3900D0C17D /* zip_unchange_archive.mdoc */, - 4B28AA1E15BACC3900D0C17D /* zip_unchange.mdoc */, - 4B28AA1F15BACC3900D0C17D /* zipcmp.mdoc */, - 4B28AA2015BACC3900D0C17D /* zipmerge.mdoc */, - ); - name = man; - path = ../man; - sourceTree = ""; - }; - 4B28AA2815BAD4F800D0C17D /* info */ = { - isa = PBXGroup; - children = ( - 4B939965246553FD00AEBDA4 /* appveyor.yml */, - 4B28AA2215BAD4E200D0C17D /* API-CHANGES.md */, - 4B28AA2315BAD4E200D0C17D /* AUTHORS */, - 4B28AA2415BAD4E200D0C17D /* NEWS.md */, - 4B28AA2515BAD4E200D0C17D /* README.md */, - 4B51DDB21FDADEDF00C5CA85 /* INSTALL.md */, - 4B28AA2615BAD4E200D0C17D /* THANKS */, - 4B28AA2715BAD4E200D0C17D /* TODO.md */, - 4BFF2B341FDEEF8B006EF3F3 /* CMakeLists.txt */, - 4BFF2B351FDEF277006EF3F3 /* cmake-config.h.in */, - 4BFF2B361FDEF277006EF3F3 /* cmake-zipconf.h.in */, - ); - name = info; - path = ..; - sourceTree = ""; - }; - 4BACD57415BC2AA100920691 /* regress */ = { - isa = PBXGroup; - children = ( - 4BACD57715BC2AEF00920691 /* add_from_filep.c */, - 4BFF2B541FE13033006EF3F3 /* can_clone_file.c */, - 4BC03FA01FDD5660003C7B62 /* cleanup.cmake */, - 4BC03F9D1FDD5617003C7B62 /* CMakeLists.txt */, - 4BACD57A15BC2AEF00920691 /* fopen_unchanged.c */, - 4BACD57B15BC2AEF00920691 /* fread.c */, - 4BC03F9A1FDD5617003C7B62 /* fseek.c */, - 4BD6CB5E19E71B3B00710654 /* hole.c */, - 4BC03F9C1FDD5617003C7B62 /* malloc.c */, - 4B4CB5572483D7B7005C5428 /* nihtest.conf.in */, - 4BD155CE191CD28D0046F012 /* NiHTest.pm */, - 4BC03F991FDD5617003C7B62 /* nonrandomopen.c */, - 4BC03F9B1FDD5617003C7B62 /* nonrandomopentest.c */, - 4BD155CF191CD28D0046F012 /* runtest.in */, - 4BD6CB5C19E6A5D900710654 /* source_hole.c */, - 4BD35E401A33362A00256CB7 /* test cases */, - 4BACD58415BC2AEF00920691 /* tryopen.c */, - 4B51DDB31FDAE1DB00C5CA85 /* ziptool_regress.c */, - ); - name = regress; - path = ../regress; - sourceTree = ""; - }; - 4BD35E401A33362A00256CB7 /* test cases */ = { - isa = PBXGroup; - children = ( - 4BD35E411A33366200256CB7 /* add_dir.test */, - 4BD35E421A33366200256CB7 /* add_from_buffer.test */, - 4BD35E431A33366200256CB7 /* add_from_file_duplicate.test */, - 4BD35E441A33366200256CB7 /* add_from_file_twice_duplicate.test */, - 4BD35E451A33366200256CB7 /* add_from_file.test */, - 4BD35E461A33366200256CB7 /* add_from_filep.test */, - 4BD35E471A33366200256CB7 /* add_from_stdin.test */, - 4BD35E481A33366200256CB7 /* add_from_zip_closed.test */, - 4BD35E491A33366200256CB7 /* add_from_zip_deflated.test */, - 4BC03F891FDD55C3003C7B62 /* add_from_zip_deflated2.test */, - 4BD35E4A1A33366200256CB7 /* add_from_zip_partial_deflated.test */, - 4BD35E4B1A33366200256CB7 /* add_from_zip_partial_stored.test */, - 4BD35E4C1A33366200256CB7 /* add_from_zip_stored.test */, - 4BD35E4E1A33366200256CB7 /* add_stored_in_memory.test */, - 4BD35E4F1A33366200256CB7 /* add_stored.test */, - 4BC03F8E1FDD55C4003C7B62 /* buffer-fragment-read.test */, - 4BC03F941FDD55C5003C7B62 /* buffer-fragment-write.test */, - 4B00CA28242F5C2500E0B71C /* cancel_45.test */, - 4B00CA2B242F5C2500E0B71C /* cancel_90.test */, - 4B77E61D1FDEDEA5006786BA /* clone-buffer-add.test */, - 4B77E61B1FDEDEA4006786BA /* clone-buffer-delete.test */, - 4B77E61C1FDEDEA5006786BA /* clone-buffer-replace.test */, - 4B41A2651FE15E99005D8C91 /* clone-fs-add.test */, - 4B41A2661FE15FCE005D8C91 /* clone-fs-delete.test */, - 4B41A2671FE1604E005D8C91 /* clone-fs-replace.test */, - 4BD35E501A33366200256CB7 /* cm-default.test */, - 4BC03F9F1FDD5642003C7B62 /* count_entries.test */, - 4BC03F921FDD55C4003C7B62 /* decrypt-correct-password-aes128.test */, - 4BC03F821FDD55C2003C7B62 /* decrypt-correct-password-aes192.test */, - 4BC03F951FDD55C5003C7B62 /* decrypt-correct-password-aes256.test */, - 4BC03F7A1FDD55C1003C7B62 /* decrypt-correct-password-pkware.test */, - 4BC03F931FDD55C5003C7B62 /* decrypt-no-password-aes256.test */, - 4BC03F8F1FDD55C4003C7B62 /* decrypt-wrong-password-aes128.test */, - 4BC03F791FDD55C1003C7B62 /* decrypt-wrong-password-aes192.test */, - 4BC03F8A1FDD55C3003C7B62 /* decrypt-wrong-password-aes256.test */, - 4BC03F7C1FDD55C1003C7B62 /* decrypt-wrong-password-pkware.test */, - 4BD35E511A33366200256CB7 /* delete_add_same.test */, - 4BD35E521A33366200256CB7 /* delete_invalid.test */, - 4BD35E531A33366200256CB7 /* delete_last.test */, - 4BD35E541A33366200256CB7 /* delete_multiple_last.test */, - 4BD35E551A33366200256CB7 /* delete_multiple_partial.test */, - 4BD35E561A33366200256CB7 /* delete_renamed_rename.test */, - 4BD35E5B1A33366200256CB7 /* encrypt.test */, - 4BC03F8B1FDD55C3003C7B62 /* encryption-nonrandom-aes128.test */, - 4BC03F7B1FDD55C1003C7B62 /* encryption-nonrandom-aes192.test */, - 4BC03F831FDD55C2003C7B62 /* encryption-nonrandom-aes256.test */, - 4B00CA2A242F5C2500E0B71C /* encryption-nonrandom-pkware.test */, - 4BC03F881FDD55C3003C7B62 /* encryption-remove.test */, - 4BD35E5C1A33366200256CB7 /* extra_add_multiple.test */, - 4BD35E5D1A33366200256CB7 /* extra_add.test */, - 4BD35E5E1A33366200256CB7 /* extra_count_by_id.test */, - 4BD35E5F1A33366200256CB7 /* extra_count_ignore_zip64.test */, - 4BD35E601A33366200256CB7 /* extra_count.test */, - 4BD35E611A33366200256CB7 /* extra_delete_by_id.test */, - 4BD35E621A33366200256CB7 /* extra_delete.test */, - 4BC03F9E1FDD5642003C7B62 /* extra_field_align.test */, - 4BD35E631A33366200256CB7 /* extra_get_by_id.test */, - 4BD35E641A33366200256CB7 /* extra_get.test */, - 4BD35E651A33366200256CB7 /* extra_set_modify_c.test */, - 4BD35E661A33366200256CB7 /* extra_set_modify_l.test */, - 4BD35E671A33366200256CB7 /* extra_set.test */, - 4BC03F901FDD55C4003C7B62 /* fdopen_ok.test */, - 4BD35E681A33366200256CB7 /* file_comment_encmismatch.test */, - 4BD35E691A33366200256CB7 /* fopen_unchanged.test */, - 4BD35E6A1A33366200256CB7 /* fread.test */, - 4BC03F871FDD55C3003C7B62 /* fseek_deflated.test */, - 4BC03F7F1FDD55C2003C7B62 /* fseek_fail.test */, - 4BC03F841FDD55C2003C7B62 /* fseek_ok.test */, - 4BD35E6B1A33366200256CB7 /* get_comment.test */, - 4BC03F911FDD55C4003C7B62 /* junk_at_end.test */, - 4BC03F8D1FDD55C4003C7B62 /* junk_at_start.test */, - 4BD35E8E1A33366200256CB7 /* name_locate.test */, - 4BD35E8F1A33366200256CB7 /* open_cons_extrabytes.test */, - 4BD35E901A33366200256CB7 /* open_empty_2.test */, - 4BD35E911A33366200256CB7 /* open_empty.test */, - 4BD35E921A33366200256CB7 /* open_extrabytes.test */, - 4BC03F7E1FDD55C1003C7B62 /* open_file_count.test */, - 4BD35E931A33366200256CB7 /* open_filename_duplicate_consistency.test */, - 4BD35E941A33366200256CB7 /* open_filename_duplicate_empty_consistency.test */, - 4BD35E951A33366200256CB7 /* open_filename_duplicate_empty.test */, - 4BD35E961A33366200256CB7 /* open_filename_duplicate.test */, - 4BD35E971A33366200256CB7 /* open_filename_empty.test */, - 4BD35E981A33366200256CB7 /* open_incons.test */, - 4BC03F961FDD55C5003C7B62 /* open_many_fail.test */, - 4BD35E991A33366200256CB7 /* open_many_ok.test */, - 4BC03F801FDD55C2003C7B62 /* open_multidisk.test */, - 4BD35E9A1A33366200256CB7 /* open_new_but_exists.test */, - 4BD35E9B1A33366200256CB7 /* open_new_ok.test */, - 4BD35E9C1A33366200256CB7 /* open_nonarchive.test */, - 4BD35E9D1A33366200256CB7 /* open_nosuchfile.test */, - 4BD35E9E1A33366200256CB7 /* open_ok.test */, - 4BD35E9F1A33366200256CB7 /* open_too_short.test */, - 4BD35EA01A33366200256CB7 /* open_truncate.test */, - 4BC03F861FDD55C3003C7B62 /* open_zip64_3mf.test */, - 4BD35EA11A33366200256CB7 /* open_zip64_ok.test */, - 4BC03F7D1FDD55C1003C7B62 /* preload.test */, - 4BC03F8C1FDD55C4003C7B62 /* progress.test */, - 4BD35EA21A33366200256CB7 /* rename_ascii.test */, - 4BD35EA31A33366200256CB7 /* rename_cp437.test */, - 4BD35EA41A33366200256CB7 /* rename_deleted.test */, - 4BD35EA51A33366200256CB7 /* rename_fail.test */, - 4BD35EA61A33366200256CB7 /* rename_ok.test */, - 4BD35EA81A33366200256CB7 /* rename_utf8_encmismatch.test */, - 4BD35EA91A33366200256CB7 /* rename_utf8.test */, - 4BD35EAA1A33366200256CB7 /* set_comment_all.test */, - 4BD35EAB1A33366200256CB7 /* set_comment_localonly.test */, - 4BD35EAC1A33366200256CB7 /* set_comment_removeglobal.test */, - 4BD35EAD1A33366200256CB7 /* set_comment_revert.test */, - 4BC03F971FDD55C5003C7B62 /* set_compression_bzip2_to_deflate.test */, - 4BC03F811FDD55C2003C7B62 /* set_compression_deflate_to_bzip2.test */, - 4BD35EAE1A33366200256CB7 /* set_compression_deflate_to_deflate.test */, - 4BD35EAF1A33366200256CB7 /* set_compression_deflate_to_store.test */, - 4BC03F851FDD55C3003C7B62 /* set_compression_store_to_bzip2.test */, - 4BD35EB01A33366200256CB7 /* set_compression_store_to_deflate.test */, - 4BD35EB11A33366200256CB7 /* set_compression_store_to_store.test */, - 4B00CA29242F5C2500E0B71C /* set_compression_store_to_xz.test */, - 4BD35EB21A33366200256CB7 /* set_compression_unknown.test */, - 4B00CA2C242F5C2500E0B71C /* set_compression_xz_to_store.test */, - 4B00CA27242F5C2500E0B71C /* set_file_dostime.test */, - 4BC03F981FDD55C5003C7B62 /* set_file_mtime.test */, - 4BD35EB41A33366200256CB7 /* stat_index_cp437_guess.test */, - 4BD35EB51A33366200256CB7 /* stat_index_cp437_raw.test */, - 4BD35EB61A33366200256CB7 /* stat_index_cp437_strict.test */, - 4BD35EB71A33366200256CB7 /* stat_index_fileorder.test */, - 4BD35EB81A33366200256CB7 /* stat_index_streamed_zip64.test */, - 4BD35EB91A33366200256CB7 /* stat_index_streamed.test */, - 4BD35EBA1A33366200256CB7 /* stat_index_utf8_guess.test */, - 4BD35EBB1A33366200256CB7 /* stat_index_utf8_raw.test */, - 4BD35EBC1A33366200256CB7 /* stat_index_utf8_strict.test */, - 4BD35EBD1A33366200256CB7 /* stat_index_utf8_unmarked_strict.test */, - 4BD35EBE1A33366200256CB7 /* stat_index_zip64.test */, - 4BD35EE11A33366300256CB7 /* utf-8-standardization.test */, - 4BC03F781FDD55C1003C7B62 /* zip-in-archive-comment.test */, - 4BD35EE31A33366300256CB7 /* zip64_creation.test */, - 4BD35EE41A33366300256CB7 /* zip64_stored_creation.test */, - ); - name = "test cases"; - sourceTree = ""; - }; - 4BDC71BD15B181DA00236D3C = { - isa = PBXGroup; - children = ( - 4B3FAE832385C9E900192D6A /* README Xcode Project.md */, - 4B28AA2815BAD4F800D0C17D /* info */, - 4BDC71E415B182B200236D3C /* Supporting Files */, - 3D7E353E1B33063600022624 /* examples */, - 4BDC71E315B182B200236D3C /* libzip */, - 4B01D72015B2F54C002D5007 /* src */, - 4BACD57415BC2AA100920691 /* regress */, - 4B28A9EA15BACBE100D0C17D /* man */, - 4BDC71CA15B181DA00236D3C /* Frameworks */, - 4BDC71C915B181DA00236D3C /* Products */, - ); - sourceTree = ""; - tabWidth = 8; - }; - 4BDC71C915B181DA00236D3C /* Products */ = { - isa = PBXGroup; - children = ( - 4B01D68B15B2F3F1002D5007 /* libzip.framework */, - 4B01D6FD15B2F4B1002D5007 /* zipmerge */, - 4B01D71315B2F4EB002D5007 /* zipcmp */, - 4BACD58915BC2CEA00920691 /* ziptool */, - 4BACD5C115BC2DC900920691 /* add_from_filep */, - 4BACD5D015BC2DF200920691 /* fopen_unchanged */, - 4BACD5DF15BC2F3700920691 /* fread */, - 4BACD65015BC301300920691 /* tryopen */, - 4BD6CB6C19E71CD100710654 /* hole */, - 3D7E35371B3305FB00022624 /* in-memory */, - 4B51DDC01FDAE20A00C5CA85 /* ziptool_regress */, - 4BFF2B511FE12FCA006EF3F3 /* can_clone_file */, - ); - name = Products; - sourceTree = ""; - }; - 4BDC71CA15B181DA00236D3C /* Frameworks */ = { - isa = PBXGroup; - children = ( - 4B9E578724C9770C00CEE0D6 /* libzstd.1.4.5.dylib */, - 4B3FAE812385C79200192D6A /* liblzma.5.dylib */, - 4BE92AA820345E5500509BC8 /* libnettle.6.dylib */, - 4BE92AA620345E3800509BC8 /* libbz2.tbd */, - 4BE92AA420345E2E00509BC8 /* libgnutls.30.dylib */, - 3D7E35471B33076C00022624 /* libz.dylib */, - 4B01D70815B2F4CF002D5007 /* libz.dylib */, - ); - name = Frameworks; - sourceTree = ""; - }; - 4BDC71E315B182B200236D3C /* libzip */ = { - isa = PBXGroup; - children = ( - 4B77E61A1FDDCD3A006786BA /* CMakeLists.txt */, - 4BD25DA51CF58790005A9EC4 /* compat.h */, - 4BDC72A015B1B56400236D3C /* config.h */, - 4BDC71F115B1B25E00236D3C /* zip_add_dir.c */, - 4BDC71F215B1B25E00236D3C /* zip_add_entry.c */, - 4BDC71F315B1B25E00236D3C /* zip_add.c */, - 4B0454B41E8E3DF7002FA1F9 /* zip_algorithm_bzip2.c */, - 4B0454B51E8E3DF7002FA1F9 /* zip_algorithm_deflate.c */, - 4B3FAE7F2385C5A300192D6A /* zip_algorithm_xz.c */, - 4B9E577A24C7026B00CEE0D6 /* zip_algorithm_zstd.c */, - 4BCB434119E9347E0067FAA3 /* zip_buffer.c */, - 4BDC71F415B1B25E00236D3C /* zip_close.c */, - 4BE92AB1203597D700509BC8 /* zip_crypto_commoncrypto.c */, - 4BE92AB0203597D700509BC8 /* zip_crypto_commoncrypto.h */, - 4B69E6F020330D460001EEE7 /* zip_crypto_gnutls.c */, - 4B69E6F2203341D50001EEE7 /* zip_crypto_gnutls.h */, - 4B9E578424C9769F00CEE0D6 /* zip_crypto_mbedtls.c */, - 4B9E578624C9769F00CEE0D6 /* zip_crypto_mbedtls.h */, - 4BE92AAB20346B1900509BC8 /* zip_crypto_openssl.c */, - 4BE92AAA20346B1900509BC8 /* zip_crypto_openssl.h */, - 4B9E578324C9769F00CEE0D6 /* zip_crypto_win.c */, - 4B9E578524C9769F00CEE0D6 /* zip_crypto_win.h */, - 4B69E6F3203342E30001EEE7 /* zip_crypto.h */, - 4BDC71F515B1B25E00236D3C /* zip_delete.c */, - 4BDC71F615B1B25E00236D3C /* zip_dir_add.c */, - 4BDC71F715B1B25E00236D3C /* zip_dirent.c */, - 4BDC71F815B1B25E00236D3C /* zip_discard.c */, - 4BDC71F915B1B25E00236D3C /* zip_entry.c */, - 4B9E578924C9779900CEE0D6 /* zip_err_str.c */, - 4BDC71FB15B1B25E00236D3C /* zip_error_clear.c */, - 4BDC71FC15B1B25E00236D3C /* zip_error_get_sys_type.c */, - 4BDC71FD15B1B25E00236D3C /* zip_error_get.c */, - 4BDC71FE15B1B25E00236D3C /* zip_error_strerror.c */, - 4BDC71FF15B1B25E00236D3C /* zip_error_to_str.c */, - 4BDC720015B1B25E00236D3C /* zip_error.c */, - 4BDC720115B1B25E00236D3C /* zip_extra_field_api.c */, - 4BDC720215B1B25E00236D3C /* zip_extra_field.c */, - 4BDC720315B1B25E00236D3C /* zip_fclose.c */, - 4BDC720415B1B25E00236D3C /* zip_fdopen.c */, - 4BDC720515B1B25E00236D3C /* zip_file_add.c */, - 4BDC720615B1B25E00236D3C /* zip_file_error_clear.c */, - 4BDC720715B1B25E00236D3C /* zip_file_error_get.c */, - 4BDC720815B1B25E00236D3C /* zip_file_get_comment.c */, - 4B97204D188EBE85002FAFAD /* zip_file_get_external_attributes.c */, - 4BDC720915B1B25E00236D3C /* zip_file_get_offset.c */, - 4BDC720A15B1B25E00236D3C /* zip_file_rename.c */, - 4BDC720B15B1B25E00236D3C /* zip_file_replace.c */, - 4BDC720C15B1B25E00236D3C /* zip_file_set_comment.c */, - 736ED9B71E3D688C00C36873 /* zip_file_set_encryption.c */, - 4B97204E188EBE85002FAFAD /* zip_file_set_external_attributes.c */, - 4B82CED319915F360097BC18 /* zip_file_set_mtime.c */, - 4BDC720D15B1B25E00236D3C /* zip_file_strerror.c */, - 4BDC720F15B1B25E00236D3C /* zip_fopen_encrypted.c */, - 4BDC721015B1B25E00236D3C /* zip_fopen_index_encrypted.c */, - 4BDC721115B1B25E00236D3C /* zip_fopen_index.c */, - 4BDC721215B1B25E00236D3C /* zip_fopen.c */, - 4BDC721315B1B25E00236D3C /* zip_fread.c */, - 4B3A5F4D1DF96D83005A53A1 /* zip_fseek.c */, - 4B3A5F4E1DF96D83005A53A1 /* zip_ftell.c */, - 4BDC721415B1B25E00236D3C /* zip_get_archive_comment.c */, - 4BDC721515B1B25E00236D3C /* zip_get_archive_flag.c */, - 4BDC721715B1B25E00236D3C /* zip_get_encryption_implementation.c */, - 4BDC721815B1B25E00236D3C /* zip_get_file_comment.c */, - 4BDC721915B1B25E00236D3C /* zip_get_name.c */, - 4BDC721A15B1B25E00236D3C /* zip_get_num_entries.c */, - 4BDC721B15B1B25E00236D3C /* zip_get_num_files.c */, - 3D9284801C309510001EABA7 /* zip_hash.c */, - 4BCF3019199A2F820064207B /* zip_io_util.c */, - 4B908F502385BE6C00886355 /* zip_libzip_version.c */, - 4BDC721C15B1B25E00236D3C /* zip_memdup.c */, - 4B5169A722A7993D00AA4340 /* zip_mkstempm.c */, - 4BDC721D15B1B25E00236D3C /* zip_name_locate.c */, - 4BDC721E15B1B25E00236D3C /* zip_new.c */, - 4BDC721F15B1B25E00236D3C /* zip_open.c */, - 4B00CA22242F59D700E0B71C /* zip_pkware.c */, - 4BD708781EB1CF73003F351F /* zip_progress.c */, - 736ED9B81E3D688C00C36873 /* zip_random_unix.c */, - 4B939967246E842200AEBDA4 /* zip_random_uwp.c */, - 4B939966246E842200AEBDA4 /* zip_random_win32.c */, - 4BDC722015B1B25E00236D3C /* zip_rename.c */, - 4BDC722115B1B25E00236D3C /* zip_replace.c */, - 4BDC722215B1B25E00236D3C /* zip_set_archive_comment.c */, - 4BDC722315B1B25E00236D3C /* zip_set_archive_flag.c */, - 4BDC722415B1B25E00236D3C /* zip_set_default_password.c */, - 4BDC722515B1B25E00236D3C /* zip_set_file_comment.c */, - 4BDC722615B1B25E00236D3C /* zip_set_file_compression.c */, - 4BDC722715B1B25E00236D3C /* zip_set_name.c */, - 4B908F522385BE6D00886355 /* zip_source_accept_empty.c */, - 4BC03FA21FDD6B6F003C7B62 /* zip_source_begin_write_cloning.c */, - 4BCF301A199A2F820064207B /* zip_source_begin_write.c */, - 4BDC722815B1B25E00236D3C /* zip_source_buffer.c */, - 4BD5053219A0116D007DD28A /* zip_source_call.c */, - 4BDC722915B1B25E00236D3C /* zip_source_close.c */, - 4BCF301B199A2F820064207B /* zip_source_commit_write.c */, - 4B0454B61E8E3DF7002FA1F9 /* zip_source_compress.c */, - 4BDC722A15B1B25E00236D3C /* zip_source_crc.c */, - 4BDC722C15B1B25E00236D3C /* zip_source_error.c */, - 4B93995624631B3E00AEBDA4 /* zip_source_file_common.c */, - 4B93995524631B3D00AEBDA4 /* zip_source_file_stdio_named.c */, - 4B93995724631B3E00AEBDA4 /* zip_source_file_stdio.c */, - 4B93995824631B3E00AEBDA4 /* zip_source_file_stdio.h */, - 4B93996124641D5700AEBDA4 /* zip_source_file_win32_ansi.c */, - 4B93996424644E7E00AEBDA4 /* zip_source_file_win32_named.c */, - 4B93996224643F5700AEBDA4 /* zip_source_file_win32_utf8.c */, - 4B9399632464401300AEBDA4 /* zip_source_file_win32_utf16.c */, - 4B93996024640B1700AEBDA4 /* zip_source_file_win32.c */, - 4B93995F24640B1700AEBDA4 /* zip_source_file_win32.h */, - 4B93995924631B3E00AEBDA4 /* zip_source_file.h */, - 4BDC722F15B1B25E00236D3C /* zip_source_free.c */, - 4BDC723015B1B25E00236D3C /* zip_source_function.c */, - 4B5D0CD4244B154E006C2E93 /* zip_source_get_file_attributes.c */, - 4BE402AC19D94AE400298248 /* zip_source_is_deleted.c */, - 4BDC723115B1B25E00236D3C /* zip_source_layered.c */, - 4BDC723215B1B25E00236D3C /* zip_source_open.c */, - 4B00CA21242F59D700E0B71C /* zip_source_pkware_decode.c */, - 4B00CA23242F59D700E0B71C /* zip_source_pkware_encode.c */, - 4BDC723515B1B25E00236D3C /* zip_source_read.c */, - 4BCF3031199ABD3A0064207B /* zip_source_remove.c */, - 4BCF301C199A2F820064207B /* zip_source_rollback_write.c */, - 4BCF3034199ABDDA0064207B /* zip_source_seek_write.c */, - 4BCF301D199A2F820064207B /* zip_source_seek.c */, - 4BDC723615B1B25E00236D3C /* zip_source_stat.c */, - 4BCF301E199A2F820064207B /* zip_source_supports.c */, - 4BCF3035199ABDDA0064207B /* zip_source_tell_write.c */, - 4BCF301F199A2F820064207B /* zip_source_tell.c */, - 4BDC723715B1B25E00236D3C /* zip_source_window.c */, - 736ED9B91E3D688C00C36873 /* zip_source_winzip_aes_decode.c */, - 736ED9BA1E3D688C00C36873 /* zip_source_winzip_aes_encode.c */, - 4BCF3020199A2F820064207B /* zip_source_write.c */, - 4BDC723815B1B25E00236D3C /* zip_source_zip_new.c */, - 4BDC723915B1B25E00236D3C /* zip_source_zip.c */, - 4BDC723A15B1B25E00236D3C /* zip_stat_index.c */, - 4BDC723B15B1B25E00236D3C /* zip_stat_init.c */, - 4BDC723C15B1B25E00236D3C /* zip_stat.c */, - 4BDC723D15B1B25E00236D3C /* zip_strerror.c */, - 4BDC723E15B1B25E00236D3C /* zip_string.c */, - 4BDC723F15B1B25E00236D3C /* zip_unchange_all.c */, - 4BDC724015B1B25E00236D3C /* zip_unchange_archive.c */, - 4BDC724115B1B25E00236D3C /* zip_unchange_data.c */, - 4BDC724215B1B25E00236D3C /* zip_unchange.c */, - 4BDC724315B1B25E00236D3C /* zip_utf-8.c */, - 4B69E6ED2032F1870001EEE7 /* zip_winzip_aes.c */, - 4BDC729815B1B2A600236D3C /* zip.h */, - 4BDC729E15B1B4E900236D3C /* zipconf.h */, - 4BDC729915B1B2A600236D3C /* zipint.h */, - ); - name = libzip; - path = ../lib; - sourceTree = ""; - }; - 4BDC71E415B182B200236D3C /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 3D77B86517009AA1000A5794 /* extract-version.sh */, - 4B01D73D15B2FB6B002D5007 /* Info.plist */, - ); - name = "Supporting Files"; - path = libzip; - sourceTree = ""; - }; -/* End PBXGroup section */ - -/* Begin PBXHeadersBuildPhase section */ - 4B01D68815B2F3F1002D5007 /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BE92AB3203597D700509BC8 /* zip_crypto_commoncrypto.h in Headers */, - 4B93995D24631B3E00AEBDA4 /* zip_source_file_stdio.h in Headers */, - 4BE92AAD20346B1900509BC8 /* zip_crypto_openssl.h in Headers */, - 4B93995E24631B3E00AEBDA4 /* zip_source_file.h in Headers */, - 4B01D73215B2F5EE002D5007 /* zipconf.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXHeadersBuildPhase section */ - -/* Begin PBXNativeTarget section */ - 3D7E35361B3305FB00022624 /* in-memory */ = { - isa = PBXNativeTarget; - buildConfigurationList = 3D7E353D1B3305FB00022624 /* Build configuration list for PBXNativeTarget "in-memory" */; - buildPhases = ( - 3D7E35331B3305FB00022624 /* Sources */, - 3D7E35341B3305FB00022624 /* Frameworks */, - 3D7E35351B3305FB00022624 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 3D7E35451B33064500022624 /* PBXTargetDependency */, - ); - name = "in-memory"; - productName = "in-memory-example"; - productReference = 3D7E35371B3305FB00022624 /* in-memory */; - productType = "com.apple.product-type.tool"; - }; - 4B01D68A15B2F3F1002D5007 /* libzip */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4B01D69E15B2F3F1002D5007 /* Build configuration list for PBXNativeTarget "libzip" */; - buildPhases = ( - 4B01D68615B2F3F1002D5007 /* Sources */, - 4B01D68715B2F3F1002D5007 /* Frameworks */, - 4B01D68815B2F3F1002D5007 /* Headers */, - 4B01D68915B2F3F1002D5007 /* Resources */, - 3D77B86617009AC5000A5794 /* Copy Version Info from config.h */, - 4B972053188EBEB8002FAFAD /* Fix zipconf.h include. */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = libzip; - productName = "libzip Mac"; - productReference = 4B01D68B15B2F3F1002D5007 /* libzip.framework */; - productType = "com.apple.product-type.framework"; - }; - 4B01D6FC15B2F4B1002D5007 /* zipmerge */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4B01D70415B2F4B1002D5007 /* Build configuration list for PBXNativeTarget "zipmerge" */; - buildPhases = ( - 4B01D6F915B2F4B1002D5007 /* Sources */, - 4B01D6FA15B2F4B1002D5007 /* Frameworks */, - 4B01D6FB15B2F4B1002D5007 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4B01D73615B2F639002D5007 /* PBXTargetDependency */, - ); - name = zipmerge; - productName = zipmerge; - productReference = 4B01D6FD15B2F4B1002D5007 /* zipmerge */; - productType = "com.apple.product-type.tool"; - }; - 4B01D70A15B2F4EB002D5007 /* zipcmp */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4B01D71015B2F4EB002D5007 /* Build configuration list for PBXNativeTarget "zipcmp" */; - buildPhases = ( - 4B01D70B15B2F4EB002D5007 /* Sources */, - 4B01D70C15B2F4EB002D5007 /* Frameworks */, - 4B01D70F15B2F4EB002D5007 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4B01D73815B2F643002D5007 /* PBXTargetDependency */, - ); - name = zipcmp; - productName = zipcmp; - productReference = 4B01D71315B2F4EB002D5007 /* zipcmp */; - productType = "com.apple.product-type.tool"; - }; - 4B51DDB41FDAE20A00C5CA85 /* ziptool_regress */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4B51DDBD1FDAE20A00C5CA85 /* Build configuration list for PBXNativeTarget "ziptool_regress" */; - buildPhases = ( - 4B51DDB71FDAE20A00C5CA85 /* Sources */, - 4B51DDB91FDAE20A00C5CA85 /* Frameworks */, - 4B51DDBC1FDAE20A00C5CA85 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4B51DDB51FDAE20A00C5CA85 /* PBXTargetDependency */, - ); - name = ziptool_regress; - productName = ziptool; - productReference = 4B51DDC01FDAE20A00C5CA85 /* ziptool_regress */; - productType = "com.apple.product-type.tool"; - }; - 4BACD58815BC2CEA00920691 /* ziptool */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4BACD59015BC2CEA00920691 /* Build configuration list for PBXNativeTarget "ziptool" */; - buildPhases = ( - 4BACD58515BC2CEA00920691 /* Sources */, - 4BACD58615BC2CEA00920691 /* Frameworks */, - 4BACD58715BC2CEA00920691 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4BACD59715BC2D3800920691 /* PBXTargetDependency */, - ); - name = ziptool; - productName = ziptool; - productReference = 4BACD58915BC2CEA00920691 /* ziptool */; - productType = "com.apple.product-type.tool"; - }; - 4BACD5B515BC2DC900920691 /* add_from_filep */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4BACD5BE15BC2DC900920691 /* Build configuration list for PBXNativeTarget "add_from_filep" */; - buildPhases = ( - 4BACD5B815BC2DC900920691 /* Sources */, - 4BACD5BA15BC2DC900920691 /* Frameworks */, - 4BACD5BD15BC2DC900920691 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4BACD5B615BC2DC900920691 /* PBXTargetDependency */, - ); - name = add_from_filep; - productName = add_from_filep; - productReference = 4BACD5C115BC2DC900920691 /* add_from_filep */; - productType = "com.apple.product-type.tool"; - }; - 4BACD5C415BC2DF200920691 /* fopen_unchanged */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4BACD5CD15BC2DF200920691 /* Build configuration list for PBXNativeTarget "fopen_unchanged" */; - buildPhases = ( - 4BACD5C715BC2DF200920691 /* Sources */, - 4BACD5C915BC2DF200920691 /* Frameworks */, - 4BACD5CC15BC2DF200920691 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4BACD5C515BC2DF200920691 /* PBXTargetDependency */, - ); - name = fopen_unchanged; - productName = fopen_unchanged; - productReference = 4BACD5D015BC2DF200920691 /* fopen_unchanged */; - productType = "com.apple.product-type.tool"; - }; - 4BACD5D315BC2F3700920691 /* fread */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4BACD5DC15BC2F3700920691 /* Build configuration list for PBXNativeTarget "fread" */; - buildPhases = ( - 4BACD5D615BC2F3700920691 /* Sources */, - 4BACD5D815BC2F3700920691 /* Frameworks */, - 4BACD5DB15BC2F3700920691 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4BACD5D415BC2F3700920691 /* PBXTargetDependency */, - ); - name = fread; - productName = fread; - productReference = 4BACD5DF15BC2F3700920691 /* fread */; - productType = "com.apple.product-type.tool"; - }; - 4BACD64515BC301300920691 /* tryopen */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4BACD64D15BC301300920691 /* Build configuration list for PBXNativeTarget "tryopen" */; - buildPhases = ( - 4BACD64815BC301300920691 /* Sources */, - 4BACD64915BC301300920691 /* Frameworks */, - 4BACD64C15BC301300920691 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4BACD64615BC301300920691 /* PBXTargetDependency */, - ); - name = tryopen; - productName = tryopen; - productReference = 4BACD65015BC301300920691 /* tryopen */; - productType = "com.apple.product-type.tool"; - }; - 4BD6CB5F19E71CD100710654 /* hole */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4BD6CB6919E71CD100710654 /* Build configuration list for PBXNativeTarget "hole" */; - buildPhases = ( - 4BD6CB6219E71CD100710654 /* Sources */, - 4BD6CB6519E71CD100710654 /* Frameworks */, - 4BD6CB6819E71CD100710654 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - 4BD6CB6019E71CD100710654 /* PBXTargetDependency */, - ); - name = hole; - productName = hole; - productReference = 4BD6CB6C19E71CD100710654 /* hole */; - productType = "com.apple.product-type.tool"; - }; - 4BFF2B451FE12FCA006EF3F3 /* can_clone_file */ = { - isa = PBXNativeTarget; - buildConfigurationList = 4BFF2B4E1FE12FCA006EF3F3 /* Build configuration list for PBXNativeTarget "can_clone_file" */; - buildPhases = ( - 4BFF2B481FE12FCA006EF3F3 /* Sources */, - 4BFF2B4A1FE12FCA006EF3F3 /* Frameworks */, - 4BFF2B4D1FE12FCA006EF3F3 /* CopyFiles */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = can_clone_file; - productName = tryopen; - productReference = 4BFF2B511FE12FCA006EF3F3 /* can_clone_file */; - productType = "com.apple.product-type.tool"; - }; -/* End PBXNativeTarget section */ - -/* Begin PBXProject section */ - 4BDC71BF15B181DA00236D3C /* Project object */ = { - isa = PBXProject; - attributes = { - LastUpgradeCheck = 1140; - ORGANIZATIONNAME = NiH; - TargetAttributes = { - 3D7E35361B3305FB00022624 = { - CreatedOnToolsVersion = 6.3.2; - }; - }; - }; - buildConfigurationList = 4BDC71C215B181DA00236D3C /* Build configuration list for PBXProject "libzip" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = en; - hasScannedForEncodings = 0; - knownRegions = ( - en, - Base, - ); - mainGroup = 4BDC71BD15B181DA00236D3C; - productRefGroup = 4BDC71C915B181DA00236D3C /* Products */; - projectDirPath = ""; - projectRoot = ""; - targets = ( - 4B54447915C977A20067BA33 /* all */, - 4B01D72815B2F5A2002D5007 /* command line tools */, - 4BCF6A681C3BDDD500F036E9 /* examples */, - 4BACD5A715BC2D8200920691 /* test programs */, - 4B01D68A15B2F3F1002D5007 /* libzip */, - 4B01D6FC15B2F4B1002D5007 /* zipmerge */, - 4B01D70A15B2F4EB002D5007 /* zipcmp */, - 3D7E35361B3305FB00022624 /* in-memory */, - 4BACD5B515BC2DC900920691 /* add_from_filep */, - 4BFF2B451FE12FCA006EF3F3 /* can_clone_file */, - 4BACD5C415BC2DF200920691 /* fopen_unchanged */, - 4BACD5D315BC2F3700920691 /* fread */, - 4BD6CB5F19E71CD100710654 /* hole */, - 4BACD58815BC2CEA00920691 /* ziptool */, - 4BACD64515BC301300920691 /* tryopen */, - 4B51DDB41FDAE20A00C5CA85 /* ziptool_regress */, - ); - }; -/* End PBXProject section */ - -/* Begin PBXResourcesBuildPhase section */ - 4B01D68915B2F3F1002D5007 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B00CA32242F5C2500E0B71C /* set_compression_xz_to_store.test in Resources */, - 4B00CA2E242F5C2500E0B71C /* cancel_45.test in Resources */, - 4B00CA31242F5C2500E0B71C /* cancel_90.test in Resources */, - 4B00CA2D242F5C2500E0B71C /* set_file_dostime.test in Resources */, - 4B00CA30242F5C2500E0B71C /* encryption-nonrandom-pkware.test in Resources */, - 4B00CA2F242F5C2500E0B71C /* set_compression_store_to_xz.test in Resources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXResourcesBuildPhase section */ - -/* Begin PBXShellScriptBuildPhase section */ - 3D77B86617009AC5000A5794 /* Copy Version Info from config.h */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Copy Version Info from config.h"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "./extract-version.sh \"config.h\" \"Info.plist\" "; - }; - 4B972053188EBEB8002FAFAD /* Fix zipconf.h include. */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Fix zipconf.h include."; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "sed \"s,,<$EXECUTABLE_NAME/zipconf.h>,\" ../lib/zip.h > \"$BUILT_PRODUCTS_DIR/$CONTENTS_FOLDER_PATH/Headers/zip.h\""; - }; -/* End PBXShellScriptBuildPhase section */ - -/* Begin PBXSourcesBuildPhase section */ - 3D7E35331B3305FB00022624 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3D7E35431B33063F00022624 /* in-memory.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B01D68615B2F3F1002D5007 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 3D7E35491B330AD500022624 /* zip_source_is_deleted.c in Sources */, - 4BCF3039199ABDDA0064207B /* zip_source_tell_write.c in Sources */, - 4B01D6A615B2F46B002D5007 /* zip_add_dir.c in Sources */, - 4B972052188EBE85002FAFAD /* zip_file_set_external_attributes.c in Sources */, - 4B0454BA1E8E3E08002FA1F9 /* zip_source_compress.c in Sources */, - 4B3FAE802385C5CC00192D6A /* zip_algorithm_xz.c in Sources */, - 4BCF3024199A2F820064207B /* zip_source_begin_write.c in Sources */, - 4B01D6A715B2F46B002D5007 /* zip_add_entry.c in Sources */, - 4B01D6A815B2F46B002D5007 /* zip_add.c in Sources */, - 4B01D6A915B2F46B002D5007 /* zip_close.c in Sources */, - 4B01D6AA15B2F46B002D5007 /* zip_delete.c in Sources */, - 4B01D6AB15B2F46B002D5007 /* zip_dir_add.c in Sources */, - 4BD7087A1EB1CF73003F351F /* zip_progress.c in Sources */, - 4B01D6AC15B2F46B002D5007 /* zip_dirent.c in Sources */, - 4B00CA24242F59D700E0B71C /* zip_source_pkware_decode.c in Sources */, - 4B93995A24631B3E00AEBDA4 /* zip_source_file_stdio_named.c in Sources */, - 4B01D6AD15B2F46B002D5007 /* zip_discard.c in Sources */, - 4B00CA25242F59D700E0B71C /* zip_pkware.c in Sources */, - 4B01D6AE15B2F46B002D5007 /* zip_entry.c in Sources */, - 4B3A5F521DF96EB4005A53A1 /* zip_fseek.c in Sources */, - 4B01D6B015B2F46B002D5007 /* zip_error_clear.c in Sources */, - 4B01D6B115B2F46B002D5007 /* zip_error_get_sys_type.c in Sources */, - 4B01D6B215B2F46B002D5007 /* zip_error_get.c in Sources */, - 4B01D6B315B2F46B002D5007 /* zip_error_strerror.c in Sources */, - 4B01D6B415B2F46B002D5007 /* zip_error_to_str.c in Sources */, - 4B01D6B515B2F46B002D5007 /* zip_error.c in Sources */, - 736ED9BB1E3D6B6B00C36873 /* zip_source_winzip_aes_decode.c in Sources */, - 4B01D6B615B2F46B002D5007 /* zip_extra_field_api.c in Sources */, - 4B9E578A24C9779900CEE0D6 /* zip_err_str.c in Sources */, - 4B3A5F531DF96EB4005A53A1 /* zip_ftell.c in Sources */, - 4B01D6B715B2F46B002D5007 /* zip_extra_field.c in Sources */, - 4B01D6B815B2F46B002D5007 /* zip_fclose.c in Sources */, - 4B01D6B915B2F46B002D5007 /* zip_fdopen.c in Sources */, - 4B01D6BA15B2F46B002D5007 /* zip_file_add.c in Sources */, - 4B01D6BB15B2F46B002D5007 /* zip_file_error_clear.c in Sources */, - 4BCF3026199A2F820064207B /* zip_source_commit_write.c in Sources */, - 4B93995C24631B3E00AEBDA4 /* zip_source_file_stdio.c in Sources */, - 4B01D6BC15B2F46B002D5007 /* zip_file_error_get.c in Sources */, - 4B01D6BD15B2F46B002D5007 /* zip_file_get_comment.c in Sources */, - 4B01D6BE15B2F46B002D5007 /* zip_file_get_offset.c in Sources */, - 4B01D6BF15B2F46B002D5007 /* zip_file_rename.c in Sources */, - 4B0454BD1E8E3E24002FA1F9 /* zip_algorithm_deflate.c in Sources */, - 4B01D6C015B2F46B002D5007 /* zip_file_replace.c in Sources */, - 4B01D6C115B2F46B002D5007 /* zip_file_set_comment.c in Sources */, - 4BCF3033199ABD3A0064207B /* zip_source_remove.c in Sources */, - 4B01D6C215B2F46B002D5007 /* zip_file_strerror.c in Sources */, - 4B01D6C415B2F46B002D5007 /* zip_fopen_encrypted.c in Sources */, - 4BCF302A199A2F820064207B /* zip_source_seek.c in Sources */, - 4B01D6C515B2F46B002D5007 /* zip_fopen_index_encrypted.c in Sources */, - 4B01D6C615B2F46B002D5007 /* zip_fopen_index.c in Sources */, - 4B5169A822A7993E00AA4340 /* zip_mkstempm.c in Sources */, - 4B69E6EE2032F18B0001EEE7 /* zip_winzip_aes.c in Sources */, - 4B01D6C715B2F46B002D5007 /* zip_fopen.c in Sources */, - 3D9284821C309510001EABA7 /* zip_hash.c in Sources */, - 4B01D6C815B2F46B002D5007 /* zip_fread.c in Sources */, - 736ED9BF1E3D6B7C00C36873 /* zip_file_set_encryption.c in Sources */, - 4B01D6C915B2F46B002D5007 /* zip_get_archive_comment.c in Sources */, - 4B5D0CD5244B154E006C2E93 /* zip_source_get_file_attributes.c in Sources */, - 4B01D6CA15B2F46B002D5007 /* zip_get_archive_flag.c in Sources */, - 4BE92AB5203597D700509BC8 /* zip_crypto_commoncrypto.c in Sources */, - 4B0454BC1E8E3E24002FA1F9 /* zip_algorithm_bzip2.c in Sources */, - 736ED9BC1E3D6B6F00C36873 /* zip_source_winzip_aes_encode.c in Sources */, - 4BD5053419A01BB0007DD28A /* zip_source_call.c in Sources */, - 4B93995B24631B3E00AEBDA4 /* zip_source_file_common.c in Sources */, - 4BCF3030199A2F820064207B /* zip_source_write.c in Sources */, - 4B01D6CC15B2F46B002D5007 /* zip_get_encryption_implementation.c in Sources */, - 4B908F552385BE6D00886355 /* zip_source_accept_empty.c in Sources */, - 4B01D6CD15B2F46B002D5007 /* zip_get_file_comment.c in Sources */, - 4B01D6CE15B2F46B002D5007 /* zip_get_name.c in Sources */, - 4B01D6CF15B2F46B002D5007 /* zip_get_num_entries.c in Sources */, - 4B01D6D015B2F46B002D5007 /* zip_get_num_files.c in Sources */, - 4B01D6D115B2F46B002D5007 /* zip_memdup.c in Sources */, - 4B01D6D215B2F46B002D5007 /* zip_name_locate.c in Sources */, - 4B9E577C24C7202000CEE0D6 /* zip_algorithm_zstd.c in Sources */, - 4B01D6D315B2F46B002D5007 /* zip_new.c in Sources */, - 4B01D6D415B2F46B002D5007 /* zip_open.c in Sources */, - 4B01D6D515B2F46B002D5007 /* zip_rename.c in Sources */, - 4BCF302E199A2F820064207B /* zip_source_tell.c in Sources */, - 4B542C2C22B12E3900960B38 /* zip_random_unix.c in Sources */, - 4B972050188EBE85002FAFAD /* zip_file_get_external_attributes.c in Sources */, - 4B01D6D615B2F46B002D5007 /* zip_replace.c in Sources */, - 4B01D6D715B2F46B002D5007 /* zip_set_archive_comment.c in Sources */, - 4B01D6D815B2F46B002D5007 /* zip_set_archive_flag.c in Sources */, - 4B01D6D915B2F46B002D5007 /* zip_set_default_password.c in Sources */, - 4B01D6DA15B2F46B002D5007 /* zip_set_file_comment.c in Sources */, - 4B01D6DB15B2F46B002D5007 /* zip_set_file_compression.c in Sources */, - 4B01D6DC15B2F46B002D5007 /* zip_set_name.c in Sources */, - 4B01D6DD15B2F46B002D5007 /* zip_source_buffer.c in Sources */, - 4BCB434319E9347E0067FAA3 /* zip_buffer.c in Sources */, - 4B01D6DE15B2F46B002D5007 /* zip_source_close.c in Sources */, - 4B01D6DF15B2F46B002D5007 /* zip_source_crc.c in Sources */, - 4B01D6E115B2F46B002D5007 /* zip_source_error.c in Sources */, - 4B01D6E415B2F46B002D5007 /* zip_source_free.c in Sources */, - 4B01D6E515B2F46B002D5007 /* zip_source_function.c in Sources */, - 4BCF3022199A2F820064207B /* zip_io_util.c in Sources */, - 4B01D6E615B2F46B002D5007 /* zip_source_layered.c in Sources */, - 4B01D6E715B2F46B002D5007 /* zip_source_open.c in Sources */, - 4B908F532385BE6D00886355 /* zip_libzip_version.c in Sources */, - 4B01D6EA15B2F46B002D5007 /* zip_source_read.c in Sources */, - 4BC03FA41FDD6B6F003C7B62 /* zip_source_begin_write_cloning.c in Sources */, - 4B01D6EB15B2F46B002D5007 /* zip_source_stat.c in Sources */, - 4BCF302C199A2F820064207B /* zip_source_supports.c in Sources */, - 4B01D6EC15B2F46B002D5007 /* zip_source_window.c in Sources */, - 4B01D6ED15B2F46B002D5007 /* zip_source_zip_new.c in Sources */, - 4B01D6EE15B2F46B002D5007 /* zip_source_zip.c in Sources */, - 4B01D6EF15B2F46B002D5007 /* zip_stat_index.c in Sources */, - 4B01D6F015B2F46B002D5007 /* zip_stat_init.c in Sources */, - 4B82CED519915F360097BC18 /* zip_file_set_mtime.c in Sources */, - 4B01D6F115B2F46B002D5007 /* zip_stat.c in Sources */, - 4B01D6F215B2F46B002D5007 /* zip_strerror.c in Sources */, - 4B01D6F315B2F46B002D5007 /* zip_string.c in Sources */, - 4B01D6F415B2F46B002D5007 /* zip_unchange_all.c in Sources */, - 4B01D6F515B2F46B002D5007 /* zip_unchange_archive.c in Sources */, - 4BCF3028199A2F820064207B /* zip_source_rollback_write.c in Sources */, - 4BCF3037199ABDDA0064207B /* zip_source_seek_write.c in Sources */, - 4B01D6F615B2F46B002D5007 /* zip_unchange_data.c in Sources */, - 4B01D6F715B2F46B002D5007 /* zip_unchange.c in Sources */, - 4B00CA26242F59D700E0B71C /* zip_source_pkware_encode.c in Sources */, - 4B01D6F815B2F46B002D5007 /* zip_utf-8.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B01D6F915B2F4B1002D5007 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B01D72615B2F57F002D5007 /* zipmerge.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B01D70B15B2F4EB002D5007 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B01D72515B2F57B002D5007 /* zipcmp.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4B51DDB71FDAE20A00C5CA85 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B51DDC31FDAE26600C5CA85 /* source_hole.c in Sources */, - 4B51DDC21FDAE25F00C5CA85 /* ziptool_regress.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD58515BC2CEA00920691 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4B51DDC11FDAE25B00C5CA85 /* ziptool.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5B815BC2DC900920691 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD5C315BC2DE000920691 /* add_from_filep.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5C715BC2DF200920691 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD5D215BC2EFE00920691 /* fopen_unchanged.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD5D615BC2F3700920691 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD5E115BC2F4500920691 /* fread.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BACD64815BC301300920691 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BACD65315BC302500920691 /* tryopen.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BD6CB6219E71CD100710654 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BD6CB6F19E71D6900710654 /* hole.c in Sources */, - 4BD6CB6419E71CD100710654 /* source_hole.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 4BFF2B481FE12FCA006EF3F3 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 4BFF2B551FE13033006EF3F3 /* can_clone_file.c in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXSourcesBuildPhase section */ - -/* Begin PBXTargetDependency section */ - 3D7E35451B33064500022624 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 3D7E35441B33064500022624 /* PBXContainerItemProxy */; - }; - 4B01D72D15B2F5AC002D5007 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D6FC15B2F4B1002D5007 /* zipmerge */; - targetProxy = 4B01D72C15B2F5AC002D5007 /* PBXContainerItemProxy */; - }; - 4B01D72F15B2F5AC002D5007 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D70A15B2F4EB002D5007 /* zipcmp */; - targetProxy = 4B01D72E15B2F5AC002D5007 /* PBXContainerItemProxy */; - }; - 4B01D73615B2F639002D5007 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4B01D73515B2F639002D5007 /* PBXContainerItemProxy */; - }; - 4B01D73815B2F643002D5007 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4B01D73715B2F643002D5007 /* PBXContainerItemProxy */; - }; - 4B2CADAC1C50D57800291DE6 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BACD58815BC2CEA00920691 /* ziptool */; - targetProxy = 4B2CADAB1C50D57800291DE6 /* PBXContainerItemProxy */; - }; - 4B51DDB51FDAE20A00C5CA85 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4B51DDB61FDAE20A00C5CA85 /* PBXContainerItemProxy */; - }; - 4B51DDC51FDAE2F000C5CA85 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B51DDB41FDAE20A00C5CA85 /* ziptool_regress */; - targetProxy = 4B51DDC41FDAE2F000C5CA85 /* PBXContainerItemProxy */; - }; - 4B54447F15C977AF0067BA33 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D72815B2F5A2002D5007 /* command line tools */; - targetProxy = 4B54447E15C977AF0067BA33 /* PBXContainerItemProxy */; - }; - 4B54448115C977B10067BA33 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BACD5A715BC2D8200920691 /* test programs */; - targetProxy = 4B54448015C977B10067BA33 /* PBXContainerItemProxy */; - }; - 4BACD59715BC2D3800920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4BACD59615BC2D3800920691 /* PBXContainerItemProxy */; - }; - 4BACD5B615BC2DC900920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4BACD5B715BC2DC900920691 /* PBXContainerItemProxy */; - }; - 4BACD5C515BC2DF200920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4BACD5C615BC2DF200920691 /* PBXContainerItemProxy */; - }; - 4BACD5D415BC2F3700920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4BACD5D515BC2F3700920691 /* PBXContainerItemProxy */; - }; - 4BACD64615BC301300920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4BACD64715BC301300920691 /* PBXContainerItemProxy */; - }; - 4BACD65515BC303B00920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BACD5B515BC2DC900920691 /* add_from_filep */; - targetProxy = 4BACD65415BC303B00920691 /* PBXContainerItemProxy */; - }; - 4BACD65715BC303B00920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BACD5C415BC2DF200920691 /* fopen_unchanged */; - targetProxy = 4BACD65615BC303B00920691 /* PBXContainerItemProxy */; - }; - 4BACD65915BC303B00920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BACD5D315BC2F3700920691 /* fread */; - targetProxy = 4BACD65815BC303B00920691 /* PBXContainerItemProxy */; - }; - 4BACD66915BC303B00920691 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BACD64515BC301300920691 /* tryopen */; - targetProxy = 4BACD66815BC303B00920691 /* PBXContainerItemProxy */; - }; - 4BCF6A791C3BDDF900F036E9 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 3D7E35361B3305FB00022624 /* in-memory */; - targetProxy = 4BCF6A781C3BDDF900F036E9 /* PBXContainerItemProxy */; - }; - 4BCF6A7B1C3BDDFF00F036E9 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BCF6A681C3BDDD500F036E9 /* examples */; - targetProxy = 4BCF6A7A1C3BDDFF00F036E9 /* PBXContainerItemProxy */; - }; - 4BD6CB6019E71CD100710654 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4B01D68A15B2F3F1002D5007 /* libzip */; - targetProxy = 4BD6CB6119E71CD100710654 /* PBXContainerItemProxy */; - }; - 4BD6CB6E19E71D0800710654 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BD6CB5F19E71CD100710654 /* hole */; - targetProxy = 4BD6CB6D19E71D0800710654 /* PBXContainerItemProxy */; - }; - 4BFF2B531FE13002006EF3F3 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 4BFF2B451FE12FCA006EF3F3 /* can_clone_file */; - targetProxy = 4BFF2B521FE13002006EF3F3 /* PBXContainerItemProxy */; - }; -/* End PBXTargetDependency section */ - -/* Begin XCBuildConfiguration section */ - 3D7E353B1B3305FB00022624 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 3D7E353C1B3305FB00022624 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 4B01D69C15B2F3F1002D5007 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; - CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; - COMBINE_HIDPI_IMAGES = YES; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = "compiler-default"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_PRECOMPILE_PREFIX_HEADER = NO; - GCC_PREFIX_HEADER = ""; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_PEDANTIC = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_SIGN_COMPARE = YES; - INFOPLIST_FILE = Info.plist; - INSTALL_PATH = "@rpath"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - /usr/local/Cellar/zstd/1.4.5/lib, - ); - PRODUCT_BUNDLE_IDENTIFIER = "at.nih.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - VERSION_INFO_BUILDER = ""; - VERSION_INFO_FILE = ""; - WRAPPER_EXTENSION = framework; - }; - name = Debug; - }; - 4B01D69D15B2F3F1002D5007 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; - CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; - COMBINE_HIDPI_IMAGES = YES; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = "compiler-default"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_PRECOMPILE_PREFIX_HEADER = NO; - GCC_PREFIX_HEADER = ""; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_PEDANTIC = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_SIGN_COMPARE = YES; - INFOPLIST_FILE = Info.plist; - INSTALL_PATH = "@rpath"; - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - /usr/local/Cellar/zstd/1.4.5/lib, - ); - PRODUCT_BUNDLE_IDENTIFIER = "at.nih.${PRODUCT_NAME:rfc1034identifier}"; - PRODUCT_NAME = "$(TARGET_NAME)"; - SKIP_INSTALL = YES; - VERSION_INFO_BUILDER = ""; - VERSION_INFO_FILE = ""; - WRAPPER_EXTENSION = framework; - }; - name = Release; - }; - 4B01D70515B2F4B1002D5007 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; - CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_C_LANGUAGE_STANDARD = c99; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_PEDANTIC = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_SIGN_COMPARE = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 4B01D70615B2F4B1002D5007 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; - CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_C_LANGUAGE_STANDARD = c99; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_PEDANTIC = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_SIGN_COMPARE = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 4B01D71115B2F4EB002D5007 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = YES; - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = zipcmp; - }; - name = Debug; - }; - 4B01D71215B2F4EB002D5007 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = YES; - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = zipcmp; - }; - name = Release; - }; - 4B01D72A15B2F5A2002D5007 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 4B01D72B15B2F5A2002D5007 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 4B51DDBE1FDAE20A00C5CA85 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 4B51DDBF1FDAE20A00C5CA85 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 4B54447A15C977A20067BA33 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 4B54447B15C977A20067BA33 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 4BACD59115BC2CEA00920691 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 4BACD59215BC2CEA00920691 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 4BACD5AF15BC2D8200920691 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - PRODUCT_NAME = "command line tools copy"; - }; - name = Debug; - }; - 4BACD5B015BC2D8200920691 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - PRODUCT_NAME = "command line tools copy"; - }; - name = Release; - }; - 4BACD5BF15BC2DC900920691 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = add_from_filep; - }; - name = Debug; - }; - 4BACD5C015BC2DC900920691 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = add_from_filep; - }; - name = Release; - }; - 4BACD5CE15BC2DF200920691 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = fopen_unchanged; - }; - name = Debug; - }; - 4BACD5CF15BC2DF200920691 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = fopen_unchanged; - }; - name = Release; - }; - 4BACD5DD15BC2F3700920691 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = fread; - }; - name = Debug; - }; - 4BACD5DE15BC2F3700920691 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = fread; - }; - name = Release; - }; - 4BACD64E15BC301300920691 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = tryopen; - }; - name = Debug; - }; - 4BACD64F15BC301300920691 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = tryopen; - }; - name = Release; - }; - 4BCF6A761C3BDDD500F036E9 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 4BCF6A771C3BDDD500F036E9 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - COMBINE_HIDPI_IMAGES = YES; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; - 4BD6CB6A19E71CD100710654 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = hole; - }; - name = Debug; - }; - 4BD6CB6B19E71CD100710654 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = hole; - }; - name = Release; - }; - 4BDC71D315B181DA00236D3C /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - GCC_C_LANGUAGE_STANDARD = "compiler-default"; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = "HAVE_CONFIG_H=1"; - GCC_SYMBOLS_PRIVATE_EXTERN = NO; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_SIGN_COMPARE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - /usr/local/include, - ../lib, - ., - ../src, - ); - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.6; - ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = ""; - SDKROOT = macosx; - USE_HEADERMAP = NO; - WARNING_CFLAGS = "-Wno-nullability-extension"; - }; - name = Debug; - }; - 4BDC71D415B181DA00236D3C /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES; - CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_COMMA = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_IMPLICIT_SIGN_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; - CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; - CLANG_WARN_STRICT_PROTOTYPES = YES; - CLANG_WARN_SUSPICIOUS_IMPLICIT_CONVERSION = YES; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; - ENABLE_STRICT_OBJC_MSGSEND = YES; - GCC_C_LANGUAGE_STANDARD = "compiler-default"; - GCC_NO_COMMON_BLOCKS = YES; - GCC_PREPROCESSOR_DEFINITIONS = "HAVE_CONFIG_H=1"; - GCC_VERSION = com.apple.compilers.llvm.clang.1_0; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_MISSING_NEWLINE = YES; - GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES; - GCC_WARN_SHADOW = YES; - GCC_WARN_SIGN_COMPARE = YES; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - HEADER_SEARCH_PATHS = ( - /usr/local/include, - ../lib, - ., - ../src, - ); - IPHONEOS_DEPLOYMENT_TARGET = 8.0; - MACOSX_DEPLOYMENT_TARGET = 10.6; - OTHER_CFLAGS = ""; - SDKROOT = macosx; - USE_HEADERMAP = NO; - VALIDATE_PRODUCT = YES; - WARNING_CFLAGS = "-Wno-nullability-extension"; - }; - name = Release; - }; - 4BFF2B4F1FE12FCA006EF3F3 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = dwarf; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - 4BFF2B501FE12FCA006EF3F3 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - CODE_SIGN_IDENTITY = "-"; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - GCC_ENABLE_OBJC_EXCEPTIONS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - LD_RUNPATH_SEARCH_PATHS = "@loader_path/"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; -/* End XCBuildConfiguration section */ - -/* Begin XCConfigurationList section */ - 3D7E353D1B3305FB00022624 /* Build configuration list for PBXNativeTarget "in-memory" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 3D7E353B1B3305FB00022624 /* Debug */, - 3D7E353C1B3305FB00022624 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4B01D69E15B2F3F1002D5007 /* Build configuration list for PBXNativeTarget "libzip" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B01D69C15B2F3F1002D5007 /* Debug */, - 4B01D69D15B2F3F1002D5007 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4B01D70415B2F4B1002D5007 /* Build configuration list for PBXNativeTarget "zipmerge" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B01D70515B2F4B1002D5007 /* Debug */, - 4B01D70615B2F4B1002D5007 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4B01D71015B2F4EB002D5007 /* Build configuration list for PBXNativeTarget "zipcmp" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B01D71115B2F4EB002D5007 /* Debug */, - 4B01D71215B2F4EB002D5007 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4B01D72915B2F5A2002D5007 /* Build configuration list for PBXAggregateTarget "command line tools" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B01D72A15B2F5A2002D5007 /* Debug */, - 4B01D72B15B2F5A2002D5007 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4B51DDBD1FDAE20A00C5CA85 /* Build configuration list for PBXNativeTarget "ziptool_regress" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B51DDBE1FDAE20A00C5CA85 /* Debug */, - 4B51DDBF1FDAE20A00C5CA85 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4B54447C15C977A20067BA33 /* Build configuration list for PBXAggregateTarget "all" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4B54447A15C977A20067BA33 /* Debug */, - 4B54447B15C977A20067BA33 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BACD59015BC2CEA00920691 /* Build configuration list for PBXNativeTarget "ziptool" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BACD59115BC2CEA00920691 /* Debug */, - 4BACD59215BC2CEA00920691 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BACD5AE15BC2D8200920691 /* Build configuration list for PBXAggregateTarget "test programs" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BACD5AF15BC2D8200920691 /* Debug */, - 4BACD5B015BC2D8200920691 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BACD5BE15BC2DC900920691 /* Build configuration list for PBXNativeTarget "add_from_filep" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BACD5BF15BC2DC900920691 /* Debug */, - 4BACD5C015BC2DC900920691 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BACD5CD15BC2DF200920691 /* Build configuration list for PBXNativeTarget "fopen_unchanged" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BACD5CE15BC2DF200920691 /* Debug */, - 4BACD5CF15BC2DF200920691 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BACD5DC15BC2F3700920691 /* Build configuration list for PBXNativeTarget "fread" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BACD5DD15BC2F3700920691 /* Debug */, - 4BACD5DE15BC2F3700920691 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BACD64D15BC301300920691 /* Build configuration list for PBXNativeTarget "tryopen" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BACD64E15BC301300920691 /* Debug */, - 4BACD64F15BC301300920691 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BCF6A751C3BDDD500F036E9 /* Build configuration list for PBXAggregateTarget "examples" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BCF6A761C3BDDD500F036E9 /* Debug */, - 4BCF6A771C3BDDD500F036E9 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BD6CB6919E71CD100710654 /* Build configuration list for PBXNativeTarget "hole" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BD6CB6A19E71CD100710654 /* Debug */, - 4BD6CB6B19E71CD100710654 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BDC71C215B181DA00236D3C /* Build configuration list for PBXProject "libzip" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BDC71D315B181DA00236D3C /* Debug */, - 4BDC71D415B181DA00236D3C /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 4BFF2B4E1FE12FCA006EF3F3 /* Build configuration list for PBXNativeTarget "can_clone_file" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 4BFF2B4F1FE12FCA006EF3F3 /* Debug */, - 4BFF2B501FE12FCA006EF3F3 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; -/* End XCConfigurationList section */ - }; - rootObject = 4BDC71BF15B181DA00236D3C /* Project object */; -} diff --git a/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index a3025873b..000000000 --- a/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist deleted file mode 100644 index 18d981003..000000000 --- a/core/deps/libzip/developer-xcode/libzip.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEDidComputeMac32BitWarning - - - diff --git a/core/deps/libzip/developer-xcode/mkconfig-h.sh b/core/deps/libzip/developer-xcode/mkconfig-h.sh deleted file mode 100644 index 43199f73b..000000000 --- a/core/deps/libzip/developer-xcode/mkconfig-h.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/sh - -DIR=tmp.$$ - -mkdir -p $DIR/32 $DIR/64 - -(cd $DIR/32; ../../../configure CFLAGS=-m32) -(cd $DIR/64; ../../../configure CFLAGS=-m64) - -diff -D __LP64__ $DIR/32/config.h $DIR/64/config.h > config.h - -rm -r $DIR diff --git a/core/deps/libzip/developer-xcode/zip_err_str.c b/core/deps/libzip/developer-xcode/zip_err_str.c deleted file mode 100644 index c46258b4a..000000000 --- a/core/deps/libzip/developer-xcode/zip_err_str.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - This file was generated automatically by CMake - from zip.h; make changes there. -*/ - -#include "zipint.h" - -const char * const _zip_err_str[] = { - "No error", - "Multi-disk zip archives not supported", - "Renaming temporary file failed", - "Closing zip archive failed", - "Seek error", - "Read error", - "Write error", - "CRC error", - "Containing zip archive was closed", - "No such file", - "File already exists", - "Can't open file", - "Failure to create temporary file", - "Zlib error", - "Malloc failure", - "Entry has been changed", - "Compression method not supported", - "Premature end of file", - "Invalid argument", - "Not a zip archive", - "Internal error", - "Zip archive inconsistent", - "Can't remove file", - "Entry has been deleted", - "Encryption method not supported", - "Read-only archive", - "No password provided", - "Wrong password provided", - "Operation not supported", - "Resource still in use", - "Tell error", - "Compressed data invalid", - "Operation cancelled", -}; - -const int _zip_nerr_str = sizeof(_zip_err_str)/sizeof(_zip_err_str[0]); - -#define N ZIP_ET_NONE -#define S ZIP_ET_SYS -#define Z ZIP_ET_ZLIB - -const int _zip_err_type[] = { - N, - N, - S, - S, - S, - S, - S, - N, - N, - N, - N, - S, - S, - Z, - N, - N, - N, - N, - N, - N, - N, - N, - S, - N, - N, - N, - N, - N, - N, - N, - S, - N, - N, -}; diff --git a/core/deps/libzip/developer-xcode/zipconf.h b/core/deps/libzip/developer-xcode/zipconf.h deleted file mode 100644 index 99b3f07c4..000000000 --- a/core/deps/libzip/developer-xcode/zipconf.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef _HAD_ZIPCONF_H -#define _HAD_ZIPCONF_H - -/* - zipconf.h -- platform specific include file - - This file was generated automatically by CMake - based on ../cmake-zipconf.h.in. - */ - -#define LIBZIP_VERSION "1.5.2a" -#define LIBZIP_VERSION_MAJOR 1 -#define LIBZIP_VERSION_MINOR 5 -#define LIBZIP_VERSION_MICRO 2a - -/* #undef ZIP_STATIC */ - - -#define __STDC_FORMAT_MACROS 1 -#include - -typedef int8_t zip_int8_t; -typedef uint8_t zip_uint8_t; -typedef int16_t zip_int16_t; -typedef uint16_t zip_uint16_t; -typedef int32_t zip_int32_t; -typedef uint32_t zip_uint32_t; -typedef int64_t zip_int64_t; -typedef uint64_t zip_uint64_t; - -#define ZIP_INT8_MIN (-ZIP_INT8_MAX - 1) -#define ZIP_INT8_MAX 0x7f -#define ZIP_UINT8_MAX 0xff - -#define ZIP_INT16_MIN (-ZIP_INT16_MAX - 1) -#define ZIP_INT16_MAX 0x7fff -#define ZIP_UINT16_MAX 0xffff - -#define ZIP_INT32_MIN (-ZIP_INT32_MAX - 1L) -#define ZIP_INT32_MAX 0x7fffffffL -#define ZIP_UINT32_MAX 0xffffffffLU - -#define ZIP_INT64_MIN (-ZIP_INT64_MAX - 1LL) -#define ZIP_INT64_MAX 0x7fffffffffffffffLL -#define ZIP_UINT64_MAX 0xffffffffffffffffULL - -#endif /* zipconf.h */ diff --git a/core/deps/libzip/examples/CMakeLists.txt b/core/deps/libzip/examples/CMakeLists.txt index 9019e0ab6..008323236 100644 --- a/core/deps/libzip/examples/CMakeLists.txt +++ b/core/deps/libzip/examples/CMakeLists.txt @@ -1,3 +1,5 @@ -add_executable(in-memory in-memory.c) -target_link_libraries(in-memory zip) -target_include_directories(in-memory PRIVATE ${PROJECT_SOURCE_DIR}/lib ${PROJECT_BINARY_DIR}) +foreach(PROGRAM add-compressed-data autoclose-archive in-memory) + add_executable(${PROGRAM} ${PROGRAM}.c) + target_link_libraries(${PROGRAM} zip) + target_include_directories(${PROGRAM} PRIVATE BEFORE ${PROJECT_SOURCE_DIR}/lib ${PROJECT_BINARY_DIR}) +endforeach() diff --git a/core/deps/libzip/examples/add-compressed-data.c b/core/deps/libzip/examples/add-compressed-data.c new file mode 100644 index 000000000..6f5810662 --- /dev/null +++ b/core/deps/libzip/examples/add-compressed-data.c @@ -0,0 +1,171 @@ +/* + add-compressed-data.c -- add already compressed file to zip archive + Copyright (C) 2022 Dieter Baron and Thomas Klausner + + This file is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + This layered source can be used to add pre-compressed data to a zip archive. + The data is taken from the lower layer source. + Metadata (uncompressed size, crc, compression method) must be provided by the caller. +*/ + +#include +#include +#include + +#include + +struct ctx { + zip_uint64_t uncompressed_size; + zip_uint32_t crc; + zip_uint32_t compression_method; +}; + +zip_int64_t callback(zip_source_t* src, void *ud, void* data, zip_uint64_t length, zip_source_cmd_t command) { + struct ctx* ctx = (struct ctx*)ud; + + switch (command) { + case ZIP_SOURCE_FREE: + /* Free our context. */ + free(ctx); + return 0; + + case ZIP_SOURCE_STAT: { + zip_stat_t *st = (zip_stat_t *)data; + /* Fix metadata with provided values. */ + if (st->valid & ZIP_STAT_SIZE) { + st->comp_size = st->size; + st->valid |= ZIP_STAT_COMP_SIZE; + } + st->size = ctx->uncompressed_size; + st->crc = ctx->crc; + st->comp_method = ctx->compression_method; + st->valid |= ZIP_STAT_COMP_METHOD | ZIP_STAT_SIZE | ZIP_STAT_CRC; + + return 0; + } + + default: + /* For all other commands, use default implementation */ + return zip_source_pass_to_lower_layer(src, data, length, command); + } +} + +zip_source_t* create_layered_compressed_source(zip_source_t* source, zip_uint64_t uncompressed_size, zip_uint32_t crc, zip_uint32_t compression_method, zip_error_t *error) { + struct ctx* ctx = (struct ctx*)malloc(sizeof(*ctx)); + zip_source_t *compressed_source; + + /* Allocate context. */ + if (ctx == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + /* Initialize context */ + ctx->compression_method = compression_method; + ctx->uncompressed_size = uncompressed_size; + ctx->crc = crc; + + /* Create layered source using our callback and context. */ + compressed_source = zip_source_layered_create(source, callback, ctx, error); + + /* In case of error, free context. */ + if (compressed_source == NULL) { + free(ctx); + } + + return compressed_source; +} + + +/* This is the information needed to add pre-compressed data to a zip archive. data must be compressed in a format compatible with Zip (e.g. no gzip header for deflate). */ + +zip_uint16_t compression_method = ZIP_CM_DEFLATE; +zip_uint64_t uncompressed_size = 60; +zip_uint32_t crc = 0xb0354048; +zip_uint8_t data[] = { + 0x4B, 0x4C, 0x44, 0x06, 0x5C, 0x49, 0x28, 0x80, + 0x2B, 0x11, 0x55 ,0x36, 0x19, 0x05, 0x70, 0x01, + 0x00 +}; + + +int +main(int argc, char *argv[]) { + const char *archive; + zip_source_t *src, *src_comp; + zip_t *za; + int err; + + if (argc != 2) { + fprintf(stderr, "usage: %s archive\n", argv[0]); + return 1; + } + archive = argv[1]; + + if ((za = zip_open(archive, ZIP_CREATE, &err)) == NULL) { + zip_error_t error; + zip_error_init_with_code(&error, err); + fprintf(stderr, "%s: cannot open zip archive '%s': %s\n", argv[0], archive, zip_error_strerror(&error)); + zip_error_fini(&error); + exit(1); + } + + /* The data can come from any source. To keep the example simple, it is provided in a static buffer here. */ + if ((src = zip_source_buffer(za, data, sizeof(data), 0)) == NULL) { + fprintf(stderr, "%s: cannot create buffer source: %s\n", argv[0], zip_strerror(za)); + zip_discard(za); + exit(1); + } + + zip_error_t error; + if ((src_comp = create_layered_compressed_source(src, uncompressed_size, crc, compression_method, &error)) == NULL) { + fprintf(stderr, "%s: cannot create layered source: %s\n", argv[0], zip_error_strerror(&error)); + zip_source_free(src); + zip_discard(za); + exit(1); + } + + if ((zip_file_add(za, "precompressed", src_comp, 0)) < 0) { + fprintf(stderr, "%s: cannot add precompressed file: %s\n", argv[0], zip_strerror(za)); + zip_source_free(src_comp); + zip_discard(za); + exit(1); + } + + if ((zip_close(za)) < 0) { + fprintf(stderr, "%s: cannot close archive '%s': %s\n", argv[0], archive, zip_strerror(za)); + zip_discard(za); + exit(1); + } + + exit(0); +} diff --git a/core/deps/libzip/examples/autoclose-archive.c b/core/deps/libzip/examples/autoclose-archive.c new file mode 100644 index 000000000..73cc46faa --- /dev/null +++ b/core/deps/libzip/examples/autoclose-archive.c @@ -0,0 +1,160 @@ +/* + autoclose-archive.c -- automatically close archive when source is closed + Copyright (C) 2022 Dieter Baron and Thomas Klausner + + This file is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + This example layered source takes ownership of a zip archive and discards it when the source is freed. + It can be used to add files from various zip archives without having to keep track of them yourself. +*/ + + +#include +#include +#include + +#include + +struct ctx { + zip_t* archive; +}; + +zip_int64_t callback(zip_source_t* src, void *ud, void* data, zip_uint64_t length, zip_source_cmd_t command) { + struct ctx* ctx = (struct ctx*)ud; + + switch (command) { + case ZIP_SOURCE_FREE: + /* Close zip archive we took ownership of */ + zip_discard(ctx->archive); + /* Free our own context */ + free(ctx); + return 0; + + default: + /* For all other commands, use default implementation */ + return zip_source_pass_to_lower_layer(src, data, length, command); + } +} + +zip_source_t* create_layered_autoclose(zip_source_t* source, zip_t *archive, zip_error_t *error) { + struct ctx* ctx = (struct ctx*)malloc(sizeof(*ctx)); + zip_source_t *autoclose_source; + + /* Allocate context. */ + if (ctx == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + /* Initialize context */ + ctx->archive = archive; + + /* Create layered source using our callback and context. */ + autoclose_source = zip_source_layered_create(source, callback, ctx, error); + + /* In case of error, free context. */ + if (autoclose_source == NULL) { + free(ctx); + } + + return autoclose_source; +} + + +int +main(int argc, char *argv[]) { + const char *destination_archive, *source_archive, *source_file; + zip_int64_t index; + zip_source_t *src, *src_autoclose; + zip_t *z_source, *z_destination; + int err; + + if (argc != 4) { + fprintf(stderr, "usage: %s destination-archive source-archive source-file\n", argv[0]); + return 1; + } + destination_archive = argv[1]; + source_archive = argv[2]; + source_file = argv[3]; + + + if ((z_source = zip_open(source_archive, 0, &err)) == NULL) { + zip_error_t error; + zip_error_init_with_code(&error, err); + fprintf(stderr, "%s: cannot open zip archive '%s': %s\n", argv[0], source_archive, zip_error_strerror(&error)); + zip_error_fini(&error); + exit(1); + } + + if ((index = zip_name_locate(z_source, source_file, 0)) < 0) { + fprintf(stderr, "%s: cannot find file '%s' in '%s': %s\n", argv[0], source_file, source_archive, zip_strerror(z_source)); + zip_discard(z_source); + exit(1); + + } + if ((src = zip_source_zip_file(z_source, z_source, index, 0, 0, -1, NULL)) == NULL) { + fprintf(stderr, "%s: cannot open file '%s' in '%s': %s\n", argv[0], source_file, source_archive, zip_strerror(z_source)); + zip_discard(z_source); + exit(1); + } + + zip_error_t error; + if ((src_autoclose = create_layered_autoclose(src, z_source, &error)) == NULL) { + fprintf(stderr, "%s: cannot create layered source: %s\n", argv[0], zip_error_strerror(&error)); + zip_source_free(src); + zip_discard(z_source); + exit(1); + } + + if ((z_destination = zip_open(destination_archive, ZIP_CREATE, &err)) == NULL) { + zip_error_init_with_code(&error, err); + fprintf(stderr, "%s: cannot open zip archive '%s': %s\n", argv[0], destination_archive, zip_error_strerror(&error)); + zip_error_fini(&error); + zip_source_free(src_autoclose); /* freeing src_autoclose closes z_source */ + exit(1); + } + + + if ((zip_file_add(z_destination, source_file, src_autoclose, 0)) < 0) { + fprintf(stderr, "%s: cannot add file: %s\n", argv[0], zip_strerror(z_source)); + zip_source_free(src_autoclose); + zip_discard(z_destination); + exit(1); + } + + if ((zip_close(z_destination)) < 0) { + fprintf(stderr, "%s: cannot close archive '%s': %s\n", argv[0], destination_archive, zip_strerror(z_source)); + zip_discard(z_destination); + exit(1); + } + + exit(0); +} diff --git a/core/deps/libzip/examples/in-memory.c b/core/deps/libzip/examples/in-memory.c index 1a6f9fae6..e022a0b24 100644 --- a/core/deps/libzip/examples/in-memory.c +++ b/core/deps/libzip/examples/in-memory.c @@ -1,9 +1,9 @@ /* in-memory.c -- modify zip file in memory - Copyright (C) 2014-2019 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/examples/windows-open.c b/core/deps/libzip/examples/windows-open.c index 31704a661..67902484e 100644 --- a/core/deps/libzip/examples/windows-open.c +++ b/core/deps/libzip/examples/windows-open.c @@ -1,9 +1,9 @@ /* windows-open.c -- open zip archive using Windows UTF-16/Unicode file name - Copyright (C) 2015-2019 Dieter Baron and Thomas Klausner + Copyright (C) 2015-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/CMakeLists.txt b/core/deps/libzip/lib/CMakeLists.txt index 133ca67dd..1afefae00 100644 --- a/core/deps/libzip/lib/CMakeLists.txt +++ b/core/deps/libzip/lib/CMakeLists.txt @@ -86,6 +86,7 @@ add_library(zip zip_source_is_deleted.c zip_source_layered.c zip_source_open.c + zip_source_pass_to_lower_layer.c zip_source_pkware_decode.c zip_source_pkware_encode.c zip_source_read.c @@ -123,7 +124,6 @@ if(WIN32) zip_source_file_win32_utf8.c ) if(CMAKE_SYSTEM_NAME MATCHES WindowsPhone OR CMAKE_SYSTEM_NAME MATCHES WindowsStore) - target_compile_definitions(zip PRIVATE _WIN32_WINNT=0x0A00) target_sources(zip PRIVATE zip_random_uwp.c) else() target_sources(zip PRIVATE zip_source_file_win32_ansi.c zip_random_win32.c) @@ -131,7 +131,6 @@ if(WIN32) endif() else(WIN32) target_sources(zip PRIVATE - zip_mkstempm.c zip_source_file_stdio_named.c zip_random_unix.c ) @@ -149,7 +148,7 @@ endif() if(HAVE_LIBZSTD) target_sources(zip PRIVATE zip_algorithm_zstd.c) - target_link_libraries(zip PRIVATE Zstd::Zstd) + target_link_libraries(zip PRIVATE ${zstd_TARGET}) endif() if(HAVE_COMMONCRYPTO) @@ -173,7 +172,7 @@ if(HAVE_CRYPTO) endif() if(SHARED_LIB_VERSIONNING) - set_target_properties(zip PROPERTIES VERSION 5.3 SOVERSION 5) + set_target_properties(zip PROPERTIES VERSION 5.5 SOVERSION 5) endif() target_link_libraries(zip PRIVATE ZLIB::ZLIB) @@ -194,36 +193,16 @@ if(LIBZIP_DO_INSTALL) install(FILES zip.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() -# create zip_err_str.h from zip.h -file(READ ${PROJECT_SOURCE_DIR}/lib/zip.h zip_h) -string(REGEX MATCHALL "#define ZIP_ER_([A-Z_]+) ([0-9]+)[ \t]+/([-*0-9a-zA-Z ']*)/" zip_h_err ${zip_h}) -set(zip_err_str [=[ -/* - This file was generated automatically by CMake - from zip.h\; make changes there. -*/ - -#include "zipint.h" - -const char * const _zip_err_str[] = { -]=]) -set(zip_err_type) -foreach(errln ${zip_h_err}) - string(REGEX MATCH "#define ZIP_ER_([A-Z_]+) ([0-9]+)[ \t]+/([-*0-9a-zA-Z ']*)/" err_t_tt ${errln}) - string(REGEX MATCH "([N|S|Z]+) ([-0-9a-zA-Z ']*)" err_t_tt "${CMAKE_MATCH_3}") - string(APPEND zip_err_type " ${CMAKE_MATCH_1},\n") - string(STRIP "${CMAKE_MATCH_2}" err_t_tt) - string(APPEND zip_err_str " \"${err_t_tt}\",\n") -endforeach() -string(APPEND zip_err_str [=[}\; - -const int _zip_nerr_str = sizeof(_zip_err_str)/sizeof(_zip_err_str[0])\; - -#define N ZIP_ET_NONE -#define S ZIP_ET_SYS -#define Z ZIP_ET_ZLIB - -const int _zip_err_type[] = { -]=]) -string(APPEND zip_err_str "${zip_err_type}}\;\n") -file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/zip_err_str.c ${zip_err_str}) +# create zip_err_str.c from zip.h and zipint.h +add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/zip_err_str.c + COMMAND "${CMAKE_COMMAND}" + "-DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}" + "-DCMAKE_CURRENT_BINARY_DIR=${CMAKE_CURRENT_BINARY_DIR}" + "-P" "${PROJECT_SOURCE_DIR}/cmake/GenerateZipErrorStrings.cmake" + DEPENDS + ${PROJECT_SOURCE_DIR}/cmake/GenerateZipErrorStrings.cmake + ${PROJECT_SOURCE_DIR}/lib/zip.h + ${PROJECT_SOURCE_DIR}/lib/zipint.h + COMMENT "Generating zip_err_str.c" +) diff --git a/core/deps/libzip/lib/compat.h b/core/deps/libzip/lib/compat.h index 257e0ab45..384a611ff 100644 --- a/core/deps/libzip/lib/compat.h +++ b/core/deps/libzip/lib/compat.h @@ -3,10 +3,10 @@ /* compat.h -- compatibility defines. - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -41,6 +41,9 @@ /* to have *_MAX definitions for all types when compiling with g++ */ #define __STDC_LIMIT_MACROS +/* to have ISO C secure library functions */ +#define __STDC_WANT_LIB_EXT1__ 1 + #ifdef _WIN32 #ifndef ZIP_EXTERN #ifndef ZIP_STATIC @@ -94,9 +97,12 @@ typedef char bool; #if !defined(HAVE_FILENO) && defined(HAVE__FILENO) #define fileno _fileno #endif -#if defined(HAVE__SNPRINTF) +#if !defined(HAVE_SNPRINTF) && defined(HAVE__SNPRINTF) #define snprintf _snprintf #endif +#if !defined(HAVE__SNWPRINTF_S) +#define _snwprintf_s(buf, bufsz, len, fmt, ...) (_snwprintf((buf), (len), (fmt), __VA_ARGS__)) +#endif #if defined(HAVE__STRDUP) #if !defined(HAVE_STRDUP) || defined(_WIN32) #undef strdup @@ -125,6 +131,33 @@ typedef char bool; #define ftello(s) ((long)ftell((s))) #endif +#ifdef HAVE_LOCALTIME_S +#ifdef _WIN32 +/* Windows is incompatible to the C11 standard, hurray! */ +#define zip_localtime(t, tm) (localtime_s((tm), (t)) == 0 ? tm : NULL) +#else +#define zip_localtime localtime_s +#endif +#else +#ifdef HAVE_LOCALTIME_R +#define zip_localtime localtime_r +#else +#define zip_localtime(t, tm) (localtime(t)) +#endif +#endif + +#ifndef HAVE_MEMCPY_S +#define memcpy_s(dest, destsz, src, count) (memcpy((dest), (src), (count)) == NULL) +#endif + +#ifndef HAVE_SNPRINTF_S +#ifdef HAVE__SNPRINTF_S +#define snprintf_s(buf, bufsz, fmt, ...) (_snprintf_s((buf), (bufsz), (bufsz), (fmt), __VA_ARGS__)) +#else +#define snprintf_s snprintf +#endif +#endif + #if !defined(HAVE_STRCASECMP) #if defined(HAVE__STRICMP) #define strcasecmp _stricmp @@ -133,6 +166,19 @@ typedef char bool; #endif #endif +#ifndef HAVE_STRNCPY_S +#define strncpy_s(dest, destsz, src, count) (strncpy((dest), (src), (count)), 0) +#endif + +#ifndef HAVE_STRERROR_S +#define strerrorlen_s(errnum) (strlen(strerror(errnum))) +#define strerror_s(buf, bufsz, errnum) ((void)strncpy_s((buf), (bufsz), strerror(errnum), (bufsz)), (buf)[(bufsz)-1] = '\0', strerrorlen_s(errnum) >= (bufsz)) +#else +#ifndef HAVE_STRERRORLEN_S +#define strerrorlen_s(errnum) 8192 +#endif +#endif + #if SIZEOF_OFF_T == 8 #define ZIP_OFF_MAX ZIP_INT64_MAX #define ZIP_OFF_MIN ZIP_INT64_MIN diff --git a/core/deps/libzip/lib/zip.h b/core/deps/libzip/lib/zip.h index 4a1bf8912..0d31d4068 100644 --- a/core/deps/libzip/lib/zip.h +++ b/core/deps/libzip/lib/zip.h @@ -3,10 +3,10 @@ /* zip.h -- exported declarations. - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -58,6 +58,16 @@ extern "C" { #endif #endif +#ifndef ZIP_DEPRECATED +#if defined(__GNUC__) || defined(__clang__) +#define ZIP_DEPRECATED(x) __attribute__((deprecated(x))) +#elif defined(_MSC_VER) +#define ZIP_DEPRECATED(x) __declspec(deprecated(x)) +#else +#define ZIP_DEPRECATED(x) +#endif +#endif + #include #include #include @@ -77,7 +87,7 @@ extern "C" { #define ZIP_FL_NODIR 2u /* ignore directory component */ #define ZIP_FL_COMPRESSED 4u /* read compressed data */ #define ZIP_FL_UNCHANGED 8u /* use original data, ignoring changes */ -#define ZIP_FL_RECOMPRESS 16u /* force recompression of data */ +/* 16u was ZIP_FL_RECOMPRESS, which is deprecated */ #define ZIP_FL_ENCRYPTED 32u /* read encrypted data (implies ZIP_FL_COMPRESSED) */ #define ZIP_FL_ENC_GUESS 0u /* guess string encoding (is default) */ #define ZIP_FL_ENC_RAW 64u /* get unmodified string */ @@ -91,7 +101,10 @@ extern "C" { /* archive global flags flags */ -#define ZIP_AFL_RDONLY 2u /* read only -- cannot be cleared */ +#define ZIP_AFL_RDONLY 2u /* read only -- cannot be cleared */ +#define ZIP_AFL_IS_TORRENTZIP 4u /* current archive is torrentzipped */ +#define ZIP_AFL_WANT_TORRENTZIP 8u /* write archive in torrentzip format */ +#define ZIP_AFL_CREATE_OR_KEEP_FILE_FOR_EMPTY_ARCHIVE 16u /* don't remove file if archive is empty */ /* create a new extra field */ @@ -99,6 +112,10 @@ extern "C" { #define ZIP_EXTRA_FIELD_ALL ZIP_UINT16_MAX #define ZIP_EXTRA_FIELD_NEW ZIP_UINT16_MAX +/* length parameter to various functions */ + +#define ZIP_LENGTH_TO_END 0 +#define ZIP_LENGTH_UNCHECKED (-2) /* only supported by zip_source_file and its variants */ /* libzip error codes */ @@ -123,7 +140,7 @@ extern "C" { #define ZIP_ER_INVAL 18 /* N Invalid argument */ #define ZIP_ER_NOZIP 19 /* N Not a zip archive */ #define ZIP_ER_INTERNAL 20 /* N Internal error */ -#define ZIP_ER_INCONS 21 /* N Zip archive inconsistent */ +#define ZIP_ER_INCONS 21 /* L Zip archive inconsistent */ #define ZIP_ER_REMOVE 22 /* S Can't remove file */ #define ZIP_ER_DELETED 23 /* N Entry has been deleted */ #define ZIP_ER_ENCRNOTSUPP 24 /* N Encryption method not supported */ @@ -135,12 +152,15 @@ extern "C" { #define ZIP_ER_TELL 30 /* S Tell error */ #define ZIP_ER_COMPRESSED_DATA 31 /* N Compressed data invalid */ #define ZIP_ER_CANCELLED 32 /* N Operation cancelled */ +#define ZIP_ER_DATA_LENGTH 33 /* N Unexpected length of data */ +#define ZIP_ER_NOT_ALLOWED 34 /* N Not allowed in torrentzip */ /* type of system error value */ -#define ZIP_ET_NONE 0 /* sys_err unused */ -#define ZIP_ET_SYS 1 /* sys_err is errno */ -#define ZIP_ET_ZLIB 2 /* sys_err is zlib error code */ +#define ZIP_ET_NONE 0 /* sys_err unused */ +#define ZIP_ET_SYS 1 /* sys_err is errno */ +#define ZIP_ET_ZLIB 2 /* sys_err is zlib error code */ +#define ZIP_ET_LIBZIP 3 /* sys_err is libzip error code */ /* compression methods */ @@ -235,12 +255,15 @@ enum zip_source_cmd { ZIP_SOURCE_RESERVED_1, /* previously used internally */ ZIP_SOURCE_BEGIN_WRITE_CLONING, /* like ZIP_SOURCE_BEGIN_WRITE, but keep part of original file */ ZIP_SOURCE_ACCEPT_EMPTY, /* whether empty files are valid archives */ - ZIP_SOURCE_GET_FILE_ATTRIBUTES /* get additional file attributes */ + ZIP_SOURCE_GET_FILE_ATTRIBUTES, /* get additional file attributes */ + ZIP_SOURCE_SUPPORTS_REOPEN /* allow reading from changed entry */ }; typedef enum zip_source_cmd zip_source_cmd_t; #define ZIP_SOURCE_MAKE_COMMAND_BITMASK(cmd) (((zip_int64_t)1) << (cmd)) +#define ZIP_SOURCE_CHECK_SUPPORTED(supported, cmd) (((supported) & ZIP_SOURCE_MAKE_COMMAND_BITMASK(cmd)) != 0) + /* clang-format off */ #define ZIP_SOURCE_SUPPORTS_READABLE (ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_OPEN) \ @@ -344,24 +367,29 @@ typedef struct zip_buffer_fragment zip_buffer_fragment_t; typedef zip_uint32_t zip_flags_t; typedef zip_int64_t (*zip_source_callback)(void *_Nullable, void *_Nullable, zip_uint64_t, zip_source_cmd_t); +typedef zip_int64_t (*zip_source_layered_callback)(zip_source_t *_Nonnull, void *_Nullable, void *_Nullable, zip_uint64_t, enum zip_source_cmd); typedef void (*zip_progress_callback)(zip_t *_Nonnull, double, void *_Nullable); typedef int (*zip_cancel_callback)(zip_t *_Nonnull, void *_Nullable); #ifndef ZIP_DISABLE_DEPRECATED -typedef void (*zip_progress_callback_t)(double); -ZIP_EXTERN void zip_register_progress_callback(zip_t *_Nonnull, zip_progress_callback_t _Nullable); /* use zip_register_progress_callback_with_state */ +#define ZIP_FL_RECOMPRESS 16u /* force recompression of data */ -ZIP_EXTERN zip_int64_t zip_add(zip_t *_Nonnull, const char *_Nonnull, zip_source_t *_Nonnull); /* use zip_file_add */ -ZIP_EXTERN zip_int64_t zip_add_dir(zip_t *_Nonnull, const char *_Nonnull); /* use zip_dir_add */ -ZIP_EXTERN const char *_Nullable zip_get_file_comment(zip_t *_Nonnull, zip_uint64_t, int *_Nullable, int); /* use zip_file_get_comment */ -ZIP_EXTERN int zip_get_num_files(zip_t *_Nonnull); /* use zip_get_num_entries instead */ -ZIP_EXTERN int zip_rename(zip_t *_Nonnull, zip_uint64_t, const char *_Nonnull); /* use zip_file_rename */ -ZIP_EXTERN int zip_replace(zip_t *_Nonnull, zip_uint64_t, zip_source_t *_Nonnull); /* use zip_file_replace */ -ZIP_EXTERN int zip_set_file_comment(zip_t *_Nonnull, zip_uint64_t, const char *_Nullable, int); /* use zip_file_set_comment */ -ZIP_EXTERN int zip_error_get_sys_type(int); /* use zip_error_system_type */ -ZIP_EXTERN void zip_error_get(zip_t *_Nonnull, int *_Nullable, int *_Nullable); /* use zip_get_error, zip_error_code_zip / zip_error_code_system */ -ZIP_EXTERN int zip_error_to_str(char *_Nonnull, zip_uint64_t, int, int); /* use zip_error_init_with_code / zip_error_strerror */ -ZIP_EXTERN void zip_file_error_get(zip_file_t *_Nonnull, int *_Nullable, int *_Nullable); /* use zip_file_get_error, zip_error_code_zip / zip_error_code_system */ +typedef void (*zip_progress_callback_t)(double); +ZIP_DEPRECATED("use 'zip_register_progress_callback_with_state' instead") ZIP_EXTERN void zip_register_progress_callback(zip_t *_Nonnull, zip_progress_callback_t _Nullable); + +ZIP_DEPRECATED("use 'zip_file_add' instead") ZIP_EXTERN zip_int64_t zip_add(zip_t *_Nonnull, const char *_Nonnull, zip_source_t *_Nonnull); +ZIP_DEPRECATED("use 'zip_dir_add' instead") ZIP_EXTERN zip_int64_t zip_add_dir(zip_t *_Nonnull, const char *_Nonnull); +ZIP_DEPRECATED("use 'zip_file_get_comment' instead") ZIP_EXTERN const char *_Nullable zip_get_file_comment(zip_t *_Nonnull, zip_uint64_t, int *_Nullable, int); +ZIP_DEPRECATED("use 'zip_get_num_entries' instead") ZIP_EXTERN int zip_get_num_files(zip_t *_Nonnull); +ZIP_DEPRECATED("use 'zip_file_rename' instead") ZIP_EXTERN int zip_rename(zip_t *_Nonnull, zip_uint64_t, const char *_Nonnull); +ZIP_DEPRECATED("use 'zip_file_replace' instead") ZIP_EXTERN int zip_replace(zip_t *_Nonnull, zip_uint64_t, zip_source_t *_Nonnull); +ZIP_DEPRECATED("use 'zip_file_set_comment' instead") ZIP_EXTERN int zip_set_file_comment(zip_t *_Nonnull, zip_uint64_t, const char *_Nullable, int); +ZIP_DEPRECATED("use 'zip_error_init_with_code' and 'zip_error_system_type' instead") ZIP_EXTERN int zip_error_get_sys_type(int); +ZIP_DEPRECATED("use 'zip_get_error' instead") ZIP_EXTERN void zip_error_get(zip_t *_Nonnull, int *_Nullable, int *_Nullable); +ZIP_DEPRECATED("use 'zip_error_strerror' instead") ZIP_EXTERN int zip_error_to_str(char *_Nonnull, zip_uint64_t, int, int); +ZIP_DEPRECATED("use 'zip_file_get_error' instead") ZIP_EXTERN void zip_file_error_get(zip_file_t *_Nonnull, int *_Nullable, int *_Nullable); +ZIP_DEPRECATED("use 'zip_source_zip_file' instead") ZIP_EXTERN zip_source_t *_Nullable zip_source_zip(zip_t *_Nonnull, zip_t *_Nonnull, zip_uint64_t, zip_flags_t, zip_uint64_t, zip_int64_t); +ZIP_DEPRECATED("use 'zip_source_zip_file_create' instead") ZIP_EXTERN zip_source_t *_Nullable zip_source_zip_create(zip_t *_Nonnull, zip_uint64_t, zip_flags_t, zip_uint64_t, zip_int64_t, zip_error_t *_Nullable); #endif ZIP_EXTERN int zip_close(zip_t *_Nonnull); @@ -377,6 +405,7 @@ ZIP_EXTERN void zip_error_fini(zip_error_t *_Nonnull); ZIP_EXTERN void zip_error_init(zip_error_t *_Nonnull); ZIP_EXTERN void zip_error_init_with_code(zip_error_t *_Nonnull, int); ZIP_EXTERN void zip_error_set(zip_error_t *_Nullable, int, int); +ZIP_EXTERN void zip_error_set_from_source(zip_error_t *_Nonnull, zip_source_t *_Nullable); ZIP_EXTERN const char *_Nonnull zip_error_strerror(zip_error_t *_Nonnull); ZIP_EXTERN int zip_error_system_type(const zip_error_t *_Nonnull); ZIP_EXTERN zip_int64_t zip_error_to_data(const zip_error_t *_Nonnull, void *_Nonnull, zip_uint64_t); @@ -396,6 +425,7 @@ ZIP_EXTERN const zip_uint8_t *_Nullable zip_file_extra_field_get_by_id(zip_t *_N ZIP_EXTERN const char *_Nullable zip_file_get_comment(zip_t *_Nonnull, zip_uint64_t, zip_uint32_t *_Nullable, zip_flags_t); ZIP_EXTERN zip_error_t *_Nonnull zip_file_get_error(zip_file_t *_Nonnull); ZIP_EXTERN int zip_file_get_external_attributes(zip_t *_Nonnull, zip_uint64_t, zip_flags_t, zip_uint8_t *_Nullable, zip_uint32_t *_Nullable); +ZIP_EXTERN int zip_file_is_seekable(zip_file_t *_Nonnull); ZIP_EXTERN int zip_file_rename(zip_t *_Nonnull, zip_uint64_t, const char *_Nonnull, zip_flags_t); ZIP_EXTERN int zip_file_replace(zip_t *_Nonnull, zip_uint64_t, zip_source_t *_Nonnull, zip_flags_t); ZIP_EXTERN int zip_file_set_comment(zip_t *_Nonnull, zip_uint64_t, const char *_Nullable, zip_uint16_t, zip_flags_t); @@ -443,9 +473,13 @@ ZIP_EXTERN zip_source_t *_Nullable zip_source_function(zip_t *_Nonnull, zip_sour ZIP_EXTERN zip_source_t *_Nullable zip_source_function_create(zip_source_callback _Nonnull, void *_Nullable, zip_error_t *_Nullable); ZIP_EXTERN int zip_source_get_file_attributes(zip_source_t *_Nonnull, zip_file_attributes_t *_Nonnull); ZIP_EXTERN int zip_source_is_deleted(zip_source_t *_Nonnull); +ZIP_EXTERN int zip_source_is_seekable(zip_source_t *_Nonnull); ZIP_EXTERN void zip_source_keep(zip_source_t *_Nonnull); +ZIP_EXTERN zip_source_t *_Nullable zip_source_layered(zip_t *_Nullable, zip_source_t *_Nonnull, zip_source_layered_callback _Nonnull, void *_Nullable); +ZIP_EXTERN zip_source_t *_Nullable zip_source_layered_create(zip_source_t *_Nonnull, zip_source_layered_callback _Nonnull, void *_Nullable, zip_error_t *_Nullable); ZIP_EXTERN zip_int64_t zip_source_make_command_bitmap(zip_source_cmd_t, ...); ZIP_EXTERN int zip_source_open(zip_source_t *_Nonnull); +ZIP_EXTERN zip_int64_t zip_source_pass_to_lower_layer(zip_source_t *_Nonnull, void *_Nullable, zip_uint64_t, zip_source_cmd_t); ZIP_EXTERN zip_int64_t zip_source_read(zip_source_t *_Nonnull, void *_Nonnull, zip_uint64_t); ZIP_EXTERN void zip_source_rollback_write(zip_source_t *_Nonnull); ZIP_EXTERN int zip_source_seek(zip_source_t *_Nonnull, zip_int64_t, int); @@ -455,15 +489,17 @@ ZIP_EXTERN int zip_source_stat(zip_source_t *_Nonnull, zip_stat_t *_Nonnull); ZIP_EXTERN zip_int64_t zip_source_tell(zip_source_t *_Nonnull); ZIP_EXTERN zip_int64_t zip_source_tell_write(zip_source_t *_Nonnull); #ifdef _WIN32 -ZIP_EXTERN zip_source_t *zip_source_win32a(zip_t *, const char *, zip_uint64_t, zip_int64_t); -ZIP_EXTERN zip_source_t *zip_source_win32a_create(const char *, zip_uint64_t, zip_int64_t, zip_error_t *); -ZIP_EXTERN zip_source_t *zip_source_win32handle(zip_t *, void *, zip_uint64_t, zip_int64_t); -ZIP_EXTERN zip_source_t *zip_source_win32handle_create(void *, zip_uint64_t, zip_int64_t, zip_error_t *); -ZIP_EXTERN zip_source_t *zip_source_win32w(zip_t *, const wchar_t *, zip_uint64_t, zip_int64_t); -ZIP_EXTERN zip_source_t *zip_source_win32w_create(const wchar_t *, zip_uint64_t, zip_int64_t, zip_error_t *); +ZIP_EXTERN zip_source_t *_Nullable zip_source_win32a(zip_t *_Nonnull, const char *_Nonnull, zip_uint64_t, zip_int64_t); +ZIP_EXTERN zip_source_t *_Nullable zip_source_win32a_create(const char *_Nonnull, zip_uint64_t, zip_int64_t, zip_error_t *_Nullable); +ZIP_EXTERN zip_source_t *_Nullable zip_source_win32handle(zip_t *_Nonnull, void *_Nonnull, zip_uint64_t, zip_int64_t); +ZIP_EXTERN zip_source_t *_Nullable zip_source_win32handle_create(void *_Nonnull, zip_uint64_t, zip_int64_t, zip_error_t *_Nullable); +ZIP_EXTERN zip_source_t *_Nullable zip_source_win32w(zip_t *_Nonnull, const wchar_t *_Nonnull, zip_uint64_t, zip_int64_t); +ZIP_EXTERN zip_source_t *_Nullable zip_source_win32w_create(const wchar_t *_Nonnull, zip_uint64_t, zip_int64_t, zip_error_t *_Nullable); #endif +ZIP_EXTERN zip_source_t *_Nullable zip_source_window_create(zip_source_t *_Nonnull, zip_uint64_t, zip_int64_t, zip_error_t *_Nullable); ZIP_EXTERN zip_int64_t zip_source_write(zip_source_t *_Nonnull, const void *_Nullable, zip_uint64_t); -ZIP_EXTERN zip_source_t *_Nullable zip_source_zip(zip_t *_Nonnull, zip_t *_Nonnull, zip_uint64_t, zip_flags_t, zip_uint64_t, zip_int64_t); +ZIP_EXTERN zip_source_t *_Nullable zip_source_zip_file(zip_t *_Nonnull, zip_t *_Nonnull, zip_uint64_t, zip_flags_t, zip_uint64_t, zip_int64_t, const char *_Nullable); +ZIP_EXTERN zip_source_t *_Nullable zip_source_zip_file_create(zip_t *_Nonnull, zip_uint64_t, zip_flags_t, zip_uint64_t, zip_int64_t, const char *_Nullable, zip_error_t *_Nullable); ZIP_EXTERN int zip_stat(zip_t *_Nonnull, const char *_Nonnull, zip_flags_t, zip_stat_t *_Nonnull); ZIP_EXTERN int zip_stat_index(zip_t *_Nonnull, zip_uint64_t, zip_flags_t, zip_stat_t *_Nonnull); ZIP_EXTERN void zip_stat_init(zip_stat_t *_Nonnull); diff --git a/core/deps/libzip/lib/zip_add.c b/core/deps/libzip/lib/zip_add.c index ef04f1e51..9770139d6 100644 --- a/core/deps/libzip/lib/zip_add.c +++ b/core/deps/libzip/lib/zip_add.c @@ -1,9 +1,9 @@ /* zip_add.c -- add file via callback function - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_add_dir.c b/core/deps/libzip/lib/zip_add_dir.c index 9999c8da2..c31fea365 100644 --- a/core/deps/libzip/lib/zip_add_dir.c +++ b/core/deps/libzip/lib/zip_add_dir.c @@ -1,9 +1,9 @@ /* zip_add_dir.c -- add directory - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_add_entry.c b/core/deps/libzip/lib/zip_add_entry.c index 1c9fcff6a..bf12dd541 100644 --- a/core/deps/libzip/lib/zip_add_entry.c +++ b/core/deps/libzip/lib/zip_add_entry.c @@ -1,9 +1,9 @@ /* zip_add_entry.c -- create and init struct zip_entry - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_algorithm_bzip2.c b/core/deps/libzip/lib/zip_algorithm_bzip2.c index 7434e5409..f25be1438 100644 --- a/core/deps/libzip/lib/zip_algorithm_bzip2.c +++ b/core/deps/libzip/lib/zip_algorithm_bzip2.c @@ -1,9 +1,9 @@ /* zip_algorithm_bzip2.c -- bzip2 (de)compression routines - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -58,7 +58,7 @@ maximum_compressed_size(zip_uint64_t uncompressed_size) { static void * -allocate(bool compress, int compression_flags, zip_error_t *error) { +allocate(bool compress, zip_uint32_t compression_flags, zip_error_t *error) { struct ctx *ctx; if ((ctx = (struct ctx *)malloc(sizeof(*ctx))) == NULL) { @@ -67,8 +67,10 @@ allocate(bool compress, int compression_flags, zip_error_t *error) { ctx->error = error; ctx->compress = compress; - ctx->compression_flags = compression_flags; - if (ctx->compression_flags < 1 || ctx->compression_flags > 9) { + if (compression_flags >= 1 && compression_flags <= 9) { + ctx->compression_flags = (int)compression_flags; + } + else { ctx->compression_flags = 9; } ctx->end_of_input = false; @@ -82,13 +84,15 @@ allocate(bool compress, int compression_flags, zip_error_t *error) { static void * -compress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) { +compress_allocate(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error) { + (void)method; return allocate(true, compression_flags, error); } static void * -decompress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) { +decompress_allocate(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error) { + (void)method; return allocate(false, compression_flags, error); } @@ -103,6 +107,7 @@ deallocate(void *ud) { static zip_uint16_t general_purpose_bit_flags(void *ud) { + (void)ud; return 0; } @@ -132,8 +137,6 @@ map_error(int ret) { case BZ_IO_ERROR: case BZ_OUTBUFF_FULL: case BZ_SEQUENCE_ERROR: - return ZIP_ER_INTERNAL; - default: return ZIP_ER_INTERNAL; } @@ -144,6 +147,9 @@ start(void *ud, zip_stat_t *st, zip_file_attributes_t *attributes) { struct ctx *ctx = (struct ctx *)ud; int ret; + (void)st; + (void)attributes; + ctx->zstr.avail_in = 0; ctx->zstr.next_in = NULL; ctx->zstr.avail_out = 0; @@ -213,6 +219,7 @@ end_of_input(void *ud) { static zip_compression_status_t process(void *ud, zip_uint8_t *data, zip_uint64_t *length) { struct ctx *ctx = (struct ctx *)ud; + unsigned int avail_out; int ret; @@ -221,7 +228,8 @@ process(void *ud, zip_uint8_t *data, zip_uint64_t *length) { return ZIP_COMPRESSION_NEED_DATA; } - ctx->zstr.avail_out = (unsigned int)ZIP_MIN(UINT_MAX, *length); + avail_out = (unsigned int)ZIP_MIN(UINT_MAX, *length); + ctx->zstr.avail_out = avail_out; ctx->zstr.next_out = (char *)data; if (ctx->compress) { @@ -231,7 +239,7 @@ process(void *ud, zip_uint8_t *data, zip_uint64_t *length) { ret = BZ2_bzDecompress(&ctx->zstr); } - *length = *length - ctx->zstr.avail_out; + *length = avail_out - ctx->zstr.avail_out; switch (ret) { case BZ_FINISH_OK: /* compression */ diff --git a/core/deps/libzip/lib/zip_algorithm_deflate.c b/core/deps/libzip/lib/zip_algorithm_deflate.c index 99d8a597b..3c85e2045 100644 --- a/core/deps/libzip/lib/zip_algorithm_deflate.c +++ b/core/deps/libzip/lib/zip_algorithm_deflate.c @@ -1,9 +1,9 @@ /* zip_algorithm_deflate.c -- deflate (de)compression routines - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -40,7 +40,8 @@ struct ctx { zip_error_t *error; bool compress; - int compression_flags; + int level; + int mem_level; bool end_of_input; z_stream zstr; }; @@ -60,7 +61,7 @@ maximum_compressed_size(zip_uint64_t uncompressed_size) { static void * -allocate(bool compress, int compression_flags, zip_error_t *error) { +allocate(bool compress, zip_uint32_t compression_flags, zip_error_t *error) { struct ctx *ctx; if ((ctx = (struct ctx *)malloc(sizeof(*ctx))) == NULL) { @@ -70,10 +71,13 @@ allocate(bool compress, int compression_flags, zip_error_t *error) { ctx->error = error; ctx->compress = compress; - ctx->compression_flags = compression_flags; - if (ctx->compression_flags < 1 || ctx->compression_flags > 9) { - ctx->compression_flags = Z_BEST_COMPRESSION; + if (compression_flags >= 1 && compression_flags <= 9) { + ctx->level = (int)compression_flags; } + else { + ctx->level = Z_BEST_COMPRESSION; + } + ctx->mem_level = compression_flags == TORRENTZIP_COMPRESSION_FLAGS ? TORRENTZIP_MEM_LEVEL : MAX_MEM_LEVEL; ctx->end_of_input = false; ctx->zstr.zalloc = Z_NULL; @@ -85,13 +89,15 @@ allocate(bool compress, int compression_flags, zip_error_t *error) { static void * -compress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) { +compress_allocate(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error) { + (void)method; return allocate(true, compression_flags, error); } static void * -decompress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) { +decompress_allocate(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error) { + (void)method; return allocate(false, compression_flags, error); } @@ -112,10 +118,10 @@ general_purpose_bit_flags(void *ud) { return 0; } - if (ctx->compression_flags < 3) { + if (ctx->level < 3) { return 2 << 1; } - else if (ctx->compression_flags > 7) { + else if (ctx->level > 7) { return 1 << 1; } return 0; @@ -127,6 +133,9 @@ start(void *ud, zip_stat_t *st, zip_file_attributes_t *attributes) { struct ctx *ctx = (struct ctx *)ud; int ret; + (void)st; + (void)attributes; + ctx->zstr.avail_in = 0; ctx->zstr.next_in = NULL; ctx->zstr.avail_out = 0; @@ -134,7 +143,7 @@ start(void *ud, zip_stat_t *st, zip_file_attributes_t *attributes) { if (ctx->compress) { /* negative value to tell zlib not to write a header */ - ret = deflateInit2(&ctx->zstr, ctx->compression_flags, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); + ret = deflateInit2(&ctx->zstr, ctx->level, Z_DEFLATED, -MAX_WBITS, ctx->mem_level, Z_DEFAULT_STRATEGY); } else { ret = inflateInit2(&ctx->zstr, -MAX_WBITS); @@ -198,10 +207,12 @@ end_of_input(void *ud) { static zip_compression_status_t process(void *ud, zip_uint8_t *data, zip_uint64_t *length) { struct ctx *ctx = (struct ctx *)ud; + uInt avail_out; int ret; - ctx->zstr.avail_out = (uInt)ZIP_MIN(UINT_MAX, *length); + avail_out = (uInt)ZIP_MIN(UINT_MAX, *length); + ctx->zstr.avail_out = avail_out; ctx->zstr.next_out = (Bytef *)data; if (ctx->compress) { @@ -211,7 +222,7 @@ process(void *ud, zip_uint8_t *data, zip_uint64_t *length) { ret = inflate(&ctx->zstr, Z_SYNC_FLUSH); } - *length = *length - ctx->zstr.avail_out; + *length = avail_out - ctx->zstr.avail_out; switch (ret) { case Z_OK: diff --git a/core/deps/libzip/lib/zip_algorithm_xz.c b/core/deps/libzip/lib/zip_algorithm_xz.c index 25abaf2d2..d7a7142d4 100644 --- a/core/deps/libzip/lib/zip_algorithm_xz.c +++ b/core/deps/libzip/lib/zip_algorithm_xz.c @@ -1,10 +1,10 @@ /* zip_algorithm_xz.c -- LZMA/XZ (de)compression routines Bazed on zip_algorithm_deflate.c -- deflate (de)compression routines - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -104,14 +104,9 @@ maximum_compressed_size(zip_uint64_t uncompressed_size) { static void * -allocate(bool compress, int compression_flags, zip_error_t *error, zip_uint16_t method) { +allocate(bool compress, zip_uint32_t compression_flags, zip_error_t *error, zip_uint16_t method) { struct ctx *ctx; - if (compression_flags < 0) { - zip_error_set(error, ZIP_ER_INVAL, 0); - return NULL; - } - if ((ctx = (struct ctx *)malloc(sizeof(*ctx))) == NULL) { zip_error_set(error, ZIP_ER_MEMORY, 0); return NULL; @@ -119,12 +114,16 @@ allocate(bool compress, int compression_flags, zip_error_t *error, zip_uint16_t ctx->error = error; ctx->compress = compress; - ctx->compression_flags = (zip_uint32_t)compression_flags; + if (compression_flags <= 9) { + ctx->compression_flags = compression_flags; + } else { + ctx->compression_flags = 6; /* default value */ + } ctx->compression_flags |= LZMA_PRESET_EXTREME; ctx->end_of_input = false; memset(ctx->header, 0, sizeof(ctx->header)); ctx->header_bytes_offset = 0; - if (ZIP_CM_LZMA) { + if (method == ZIP_CM_LZMA) { ctx->header_state = INCOMPLETE; } else { @@ -137,13 +136,13 @@ allocate(bool compress, int compression_flags, zip_error_t *error, zip_uint16_t static void * -compress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) { +compress_allocate(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error) { return allocate(true, compression_flags, error, method); } static void * -decompress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) { +decompress_allocate(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error) { return allocate(false, compression_flags, error, method); } @@ -224,7 +223,7 @@ start(void *ud, zip_stat_t *st, zip_file_attributes_t *attributes) { zip_error_set(ctx->error, map_error(ret), 0); return false; } - + /* If general purpose bits 1 & 2 are both zero, write real uncompressed size in header. */ if ((attributes->valid & ZIP_FILE_ATTRIBUTES_GENERAL_PURPOSE_BIT_FLAGS) && (attributes->general_purpose_bit_mask & 0x6) == 0x6 && (attributes->general_purpose_bit_flags & 0x06) == 0 && (st->valid & ZIP_STAT_SIZE)) { ctx->uncompresssed_size = st->size; @@ -258,8 +257,8 @@ input(void *ud, zip_uint8_t *data, zip_uint64_t length) { /* For decompression of LZMA1: Have we read the full "lzma alone" header yet? */ if (ctx->method == ZIP_CM_LZMA && !ctx->compress && ctx->header_state == INCOMPLETE) { /* if not, get more of the data */ - zip_uint8_t got = ZIP_MIN(HEADER_BYTES_ZIP - ctx->header_bytes_offset, length); - memcpy(ctx->header + ctx->header_bytes_offset, data, got); + zip_uint8_t got = (zip_uint8_t)ZIP_MIN(HEADER_BYTES_ZIP - ctx->header_bytes_offset, length); + (void)memcpy_s(ctx->header + ctx->header_bytes_offset, sizeof(ctx->header) - ctx->header_bytes_offset, data, got); ctx->header_bytes_offset += got; length -= got; data += got; @@ -314,6 +313,7 @@ end_of_input(void *ud) { static zip_compression_status_t process(void *ud, zip_uint8_t *data, zip_uint64_t *length) { struct ctx *ctx = (struct ctx *)ud; + uInt avail_out; lzma_ret ret; /* for compression of LZMA1 */ if (ctx->method == ZIP_CM_LZMA && ctx->compress) { @@ -335,8 +335,8 @@ process(void *ud, zip_uint8_t *data, zip_uint64_t *length) { } if (ctx->header_state == OUTPUT) { /* write header */ - zip_uint8_t write_len = ZIP_MIN(HEADER_BYTES_ZIP - ctx->header_bytes_offset, *length); - memcpy(data, ctx->header + ctx->header_bytes_offset, write_len); + zip_uint8_t write_len = (zip_uint8_t)ZIP_MIN(HEADER_BYTES_ZIP - ctx->header_bytes_offset, *length); + (void)memcpy_s(data, *length, ctx->header + ctx->header_bytes_offset, write_len); ctx->header_bytes_offset += write_len; *length = write_len; if (ctx->header_bytes_offset == HEADER_BYTES_ZIP) { @@ -346,11 +346,12 @@ process(void *ud, zip_uint8_t *data, zip_uint64_t *length) { } } - ctx->zstr.avail_out = (uInt)ZIP_MIN(UINT_MAX, *length); + avail_out = (uInt)ZIP_MIN(UINT_MAX, *length); + ctx->zstr.avail_out = avail_out; ctx->zstr.next_out = (Bytef *)data; ret = lzma_code(&ctx->zstr, ctx->end_of_input ? LZMA_FINISH : LZMA_RUN); - *length = *length - ctx->zstr.avail_out; + *length = avail_out - ctx->zstr.avail_out; switch (ret) { case LZMA_OK: diff --git a/core/deps/libzip/lib/zip_algorithm_zstd.c b/core/deps/libzip/lib/zip_algorithm_zstd.c index c27e9fb39..d005da9d5 100644 --- a/core/deps/libzip/lib/zip_algorithm_zstd.c +++ b/core/deps/libzip/lib/zip_algorithm_zstd.c @@ -1,9 +1,9 @@ /* zip_algorithm_zstd.c -- zstd (de)compression routines - Copyright (C) 2020 Dieter Baron and Thomas Klausner + Copyright (C) 2020-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -33,7 +33,6 @@ #include "zipint.h" -#include #include #include #include @@ -56,21 +55,20 @@ maximum_compressed_size(zip_uint64_t uncompressed_size) { static void * -allocate(bool compress, int compression_flags, zip_error_t *error) { +allocate(bool compress, zip_uint32_t compression_flags, zip_error_t *error) { struct ctx *ctx; - /* 0: let zstd choose */ - if (compression_flags < 0 || compression_flags > 9) { - compression_flags = 0; - } - if ((ctx = (struct ctx *)malloc(sizeof(*ctx))) == NULL) { return NULL; } + ctx->compression_flags = (zip_int32_t)compression_flags; + if (ctx->compression_flags < ZSTD_minCLevel() || ctx->compression_flags > ZSTD_maxCLevel()) { + ctx->compression_flags = 0; /* let zstd choose */ + } + ctx->error = error; ctx->compress = compress; - ctx->compression_flags = compression_flags; ctx->end_of_input = false; ctx->zdstream = NULL; @@ -87,13 +85,15 @@ allocate(bool compress, int compression_flags, zip_error_t *error) { static void * -compress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) { +compress_allocate(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error) { + (void)method; return allocate(true, compression_flags, error); } static void * -decompress_allocate(zip_uint16_t method, int compression_flags, zip_error_t *error) { +decompress_allocate(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error) { + (void)method; return allocate(false, compression_flags, error); } @@ -107,7 +107,7 @@ deallocate(void *ud) { static zip_uint16_t general_purpose_bit_flags(void *ud) { - /* struct ctx *ctx = (struct ctx *)ud; */ + (void)ud; return 0; } @@ -139,6 +139,10 @@ map_error(size_t ret) { static bool start(void *ud, zip_stat_t *st, zip_file_attributes_t *attributes) { struct ctx *ctx = (struct ctx *)ud; + + (void)st; + (void)attributes; + ctx->in.src = NULL; ctx->in.pos = 0; ctx->in.size = 0; diff --git a/core/deps/libzip/lib/zip_buffer.c b/core/deps/libzip/lib/zip_buffer.c index 3bfcc82c1..e2103f04c 100644 --- a/core/deps/libzip/lib/zip_buffer.c +++ b/core/deps/libzip/lib/zip_buffer.c @@ -1,9 +1,9 @@ /* zip_buffer.c -- bounds checked access to memory buffer - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -132,13 +132,20 @@ _zip_buffer_left(zip_buffer_t *buffer) { zip_uint64_t _zip_buffer_read(zip_buffer_t *buffer, zip_uint8_t *data, zip_uint64_t length) { + zip_uint64_t copied; + if (_zip_buffer_left(buffer) < length) { length = _zip_buffer_left(buffer); } - memcpy(data, _zip_buffer_get(buffer, length), length); + copied = 0; + while (copied < length) { + size_t n = ZIP_MIN(length - copied, SIZE_MAX); + (void)memcpy_s(data + copied, n, _zip_buffer_get(buffer, n), n); + copied += n; + } - return length; + return copied; } @@ -147,8 +154,14 @@ _zip_buffer_new(zip_uint8_t *data, zip_uint64_t size) { bool free_data = (data == NULL); zip_buffer_t *buffer; +#if ZIP_UINT64_MAX > SIZE_MAX + if (size > SIZE_MAX) { + return NULL; + } +#endif + if (data == NULL) { - if ((data = (zip_uint8_t *)malloc(size)) == NULL) { + if ((data = (zip_uint8_t *)malloc((size_t)size)) == NULL) { return NULL; } } @@ -221,7 +234,7 @@ _zip_buffer_put(zip_buffer_t *buffer, const void *src, size_t length) { return -1; } - memcpy(dst, src, length); + (void)memcpy_s(dst, length, src, length); return 0; } diff --git a/core/deps/libzip/lib/zip_close.c b/core/deps/libzip/lib/zip_close.c index 5bd327b2e..ddc2c2456 100644 --- a/core/deps/libzip/lib/zip_close.c +++ b/core/deps/libzip/lib/zip_close.c @@ -1,9 +1,9 @@ /* zip_close.c -- close zip archive and update changes - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -45,6 +45,7 @@ static int add_data(zip_t *, zip_source_t *, zip_dirent_t *, zip_uint32_t); static int copy_data(zip_t *, zip_uint64_t); static int copy_source(zip_t *, zip_source_t *, zip_int64_t); +static int torrentzip_compare_names(const void *a, const void *b); static int write_cdir(zip_t *, const zip_filelist_t *, zip_uint64_t); static int write_data_descriptor(zip_t *za, const zip_dirent_t *dirent, int is_zip64); @@ -61,12 +62,12 @@ zip_close(zip_t *za) { changed = _zip_changed(za, &survivors); - /* don't create zip files with no entries */ - if (survivors == 0) { + if (survivors == 0 && !(za->ch_flags & ZIP_AFL_CREATE_OR_KEEP_FILE_FOR_EMPTY_ARCHIVE)) { + /* don't create zip files with no entries */ if ((za->open_flags & ZIP_TRUNCATE) || changed) { if (zip_source_remove(za->src) < 0) { if (!((zip_error_code_zip(zip_source_error(za->src)) == ZIP_ER_REMOVE) && (zip_error_code_system(zip_source_error(za->src)) == ENOENT))) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } } @@ -75,7 +76,8 @@ zip_close(zip_t *za) { return 0; } - if (!changed) { + /* Always write empty archive if we are told to keep it, otherwise it wouldn't be created if the file doesn't already exist. */ + if (!changed && survivors > 0) { zip_discard(za); return 0; } @@ -105,6 +107,7 @@ zip_close(zip_t *za) { } filelist[j].idx = i; + filelist[j].name = zip_get_name(za, i, 0); j++; } if (j < survivors) { @@ -113,7 +116,11 @@ zip_close(zip_t *za) { return -1; } - if ((zip_source_supports(za->src) & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_BEGIN_WRITE_CLONING)) == 0) { + if (ZIP_WANT_TORRENTZIP(za)) { + qsort(filelist, (size_t)survivors, sizeof(filelist[0]), torrentzip_compare_names); + } + + if (ZIP_WANT_TORRENTZIP(za) || (zip_source_supports(za->src) & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_BEGIN_WRITE_CLONING)) == 0) { unchanged_offset = 0; } else { @@ -146,7 +153,7 @@ zip_close(zip_t *za) { } if (unchanged_offset == 0) { if (zip_source_begin_write(za->src) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); free(filelist); return -1; } @@ -178,7 +185,7 @@ zip_close(zip_t *za) { continue; } - new_data = (ZIP_ENTRY_DATA_CHANGED(entry) || ZIP_ENTRY_CHANGED(entry, ZIP_DIRENT_COMP_METHOD) || ZIP_ENTRY_CHANGED(entry, ZIP_DIRENT_ENCRYPTION_METHOD)); + new_data = (ZIP_ENTRY_DATA_CHANGED(entry) || ZIP_ENTRY_CHANGED(entry, ZIP_DIRENT_COMP_METHOD) || ZIP_ENTRY_CHANGED(entry, ZIP_DIRENT_ENCRYPTION_METHOD)) || (ZIP_WANT_TORRENTZIP(za) && !ZIP_IS_TORRENTZIP(za)); /* create new local directory entry */ if (entry->changes == NULL) { @@ -195,8 +202,12 @@ zip_close(zip_t *za) { break; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_dirent_torrentzip_normalize(entry->changes); + } + if ((off = zip_source_tell_write(za->src)) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); error = 1; break; } @@ -207,7 +218,7 @@ zip_close(zip_t *za) { zs = NULL; if (!ZIP_ENTRY_DATA_CHANGED(entry)) { - if ((zs = _zip_source_zip_new(za, za, i, ZIP_FL_UNCHANGED, 0, 0, NULL)) == NULL) { + if ((zs = zip_source_zip_file_create(za, i, ZIP_FL_UNCHANGED, 0, -1, NULL, &za->error)) == NULL) { error = 1; break; } @@ -240,7 +251,7 @@ zip_close(zip_t *za) { break; } if (zip_source_seek(za->src, (zip_int64_t)offset, SEEK_SET) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); error = 1; break; } @@ -267,7 +278,7 @@ zip_close(zip_t *za) { if (!error) { if (zip_source_commit_write(za->src) != 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); error = 1; } _zip_progress_end(za->progress); @@ -296,7 +307,7 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { bool needs_recompress, needs_decompress, needs_crc, needs_compress, needs_reencrypt, needs_decrypt, needs_encrypt; if (zip_source_stat(src, &st) < 0) { - _zip_error_set_from_source(&za->error, src); + zip_error_set_from_source(&za->error, src); return -1; } @@ -324,6 +335,7 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { flags = ZIP_EF_LOCAL; if ((st.valid & ZIP_STAT_SIZE) == 0) { + /* TODO: not valid for torrentzip */ flags |= ZIP_FL_FORCE_ZIP64; data_length = -1; } @@ -350,6 +362,7 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { } if (max_compressed_size > 0xffffffffu) { + /* TODO: not valid for torrentzip */ flags |= ZIP_FL_FORCE_ZIP64; } } @@ -360,7 +373,7 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { } if ((offstart = zip_source_tell_write(za->src)) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } @@ -370,7 +383,7 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { return -1; } - needs_recompress = st.comp_method != ZIP_CM_ACTUAL(de->comp_method); + needs_recompress = ZIP_WANT_TORRENTZIP(za) || st.comp_method != ZIP_CM_ACTUAL(de->comp_method); needs_decompress = needs_recompress && (st.comp_method != ZIP_CM_STORE); /* in these cases we can compute the CRC ourselves, so we do */ needs_crc = (st.comp_method == ZIP_CM_STORE) || needs_decompress; @@ -383,6 +396,11 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { src_final = src; zip_source_keep(src_final); + if (!needs_decrypt && st.encryption_method == ZIP_EM_TRAD_PKWARE && (de->changed & ZIP_DIRENT_LAST_MOD)) { + /* PKWare encryption uses the last modification time for password verification, therefore we can't change it without re-encrypting. Ignoring the requested modification time change seems more sensible than failing to close the archive. */ + de->changed &= ~ZIP_DIRENT_LAST_MOD; + } + if (needs_decrypt) { zip_encryption_implementation impl; @@ -397,7 +415,6 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { return -1; } - zip_source_free(src_final); src_final = src_tmp; } @@ -407,17 +424,15 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { return -1; } - zip_source_free(src_final); src_final = src_tmp; } if (needs_crc) { - if ((src_tmp = zip_source_crc(za, src_final, 0)) == NULL) { + if ((src_tmp = zip_source_crc_create(src_final, 0, &za->error)) == NULL) { zip_source_free(src_final); return -1; } - zip_source_free(src_final); src_final = src_tmp; } @@ -427,7 +442,6 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { return -1; } - zip_source_free(src_final); src_final = src_tmp; } @@ -448,34 +462,48 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { zip_source_free(src_final); return -1; } + + if (de->encryption_method == ZIP_EM_TRAD_PKWARE) { + de->bitflags |= ZIP_GPBF_DATA_DESCRIPTOR; + + /* PKWare encryption uses last_mod, make sure it gets the right value. */ + if (de->changed & ZIP_DIRENT_LAST_MOD) { + zip_stat_t st_mtime; + zip_stat_init(&st_mtime); + st_mtime.valid = ZIP_STAT_MTIME; + st_mtime.mtime = de->last_mod; + if ((src_tmp = _zip_source_window_new(src_final, 0, -1, &st_mtime, 0, NULL, NULL, 0, true, &za->error)) == NULL) { + zip_source_free(src_final); + return -1; + } + src_final = src_tmp; + } + } + if ((src_tmp = impl(za, src_final, de->encryption_method, ZIP_CODEC_ENCODE, password)) == NULL) { /* error set by impl */ zip_source_free(src_final); return -1; } - if (de->encryption_method == ZIP_EM_TRAD_PKWARE) { - de->bitflags |= ZIP_GPBF_DATA_DESCRIPTOR; - } - zip_source_free(src_final); src_final = src_tmp; } if ((offdata = zip_source_tell_write(za->src)) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } ret = copy_source(za, src_final, data_length); if (zip_source_stat(src_final, &st) < 0) { - _zip_error_set_from_source(&za->error, src_final); + zip_error_set_from_source(&za->error, src_final); ret = -1; } if (zip_source_get_file_attributes(src_final, &attributes) != 0) { - _zip_error_set_from_source(&za->error, src_final); + zip_error_set_from_source(&za->error, src_final); ret = -1; } @@ -486,12 +514,12 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { } if ((offend = zip_source_tell_write(za->src)) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } if (zip_source_seek_write(za->src, offstart, SEEK_SET) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } @@ -512,6 +540,10 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { de->comp_size = (zip_uint64_t)(offend - offdata); _zip_dirent_apply_attributes(de, &attributes, (flags & ZIP_FL_FORCE_ZIP64) != 0, changed); + if (ZIP_WANT_TORRENTZIP(za)) { + zip_dirent_torrentzip_normalize(de); + } + if ((ret = _zip_dirent_write(za, de, flags)) < 0) return -1; @@ -522,7 +554,7 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { } if (zip_source_seek_write(za->src, offend, SEEK_SET) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } @@ -539,7 +571,6 @@ add_data(zip_t *za, zip_source_t *src, zip_dirent_t *de, zip_uint32_t changed) { static int copy_data(zip_t *za, zip_uint64_t len) { DEFINE_BYTE_ARRAY(buf, BUFSIZE); - size_t n; double total = (double)len; if (!byte_array_init(buf, BUFSIZE)) { @@ -548,7 +579,8 @@ copy_data(zip_t *za, zip_uint64_t len) { } while (len > 0) { - n = len > BUFSIZE ? BUFSIZE : len; + zip_uint64_t n = ZIP_MIN(len, BUFSIZE); + if (_zip_read(za->src, buf, n, &za->error) < 0) { byte_array_fini(buf); return -1; @@ -579,7 +611,7 @@ copy_source(zip_t *za, zip_source_t *src, zip_int64_t data_length) { int ret; if (zip_source_open(src) < 0) { - _zip_error_set_from_source(&za->error, src); + zip_error_set_from_source(&za->error, src); return -1; } @@ -606,7 +638,7 @@ copy_source(zip_t *za, zip_source_t *src, zip_int64_t data_length) { } if (n < 0) { - _zip_error_set_from_source(&za->error, src); + zip_error_set_from_source(&za->error, src); ret = -1; } @@ -619,17 +651,15 @@ copy_source(zip_t *za, zip_source_t *src, zip_int64_t data_length) { static int write_cdir(zip_t *za, const zip_filelist_t *filelist, zip_uint64_t survivors) { - zip_int64_t cd_start, end, size; - - if ((cd_start = zip_source_tell_write(za->src)) < 0) { + if (zip_source_tell_write(za->src) < 0) { return -1; } - if ((size = _zip_cdir_write(za, filelist, survivors)) < 0) { + if (_zip_cdir_write(za, filelist, survivors) < 0) { return -1; } - if ((end = zip_source_tell_write(za->src)) < 0) { + if (zip_source_tell_write(za->src) < 0) { return -1; } @@ -645,7 +675,7 @@ _zip_changed(const zip_t *za, zip_uint64_t *survivorsp) { changed = 0; survivors = 0; - if (za->comment_changed || za->ch_flags != za->flags) { + if (za->comment_changed || (ZIP_WANT_TORRENTZIP(za) && !ZIP_IS_TORRENTZIP(za))) { changed = 1; } @@ -698,3 +728,18 @@ write_data_descriptor(zip_t *za, const zip_dirent_t *de, int is_zip64) { return ret; } + + +static int torrentzip_compare_names(const void *a, const void *b) { + const char *aname = ((const zip_filelist_t *)a)->name; + const char *bname = ((const zip_filelist_t *)b)->name; + + if (aname == NULL) { + return (bname != NULL) * -1; + } + else if (bname == NULL) { + return 1; + } + + return strcasecmp(aname, bname); +} \ No newline at end of file diff --git a/core/deps/libzip/lib/zip_crypto.h b/core/deps/libzip/lib/zip_crypto.h index c42bc7b04..0d74d1a41 100644 --- a/core/deps/libzip/lib/zip_crypto.h +++ b/core/deps/libzip/lib/zip_crypto.h @@ -1,9 +1,9 @@ /* zip_crypto.h -- crypto definitions - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_crypto_commoncrypto.c b/core/deps/libzip/lib/zip_crypto_commoncrypto.c index f4c37d96b..b198be563 100644 --- a/core/deps/libzip/lib/zip_crypto_commoncrypto.c +++ b/core/deps/libzip/lib/zip_crypto_commoncrypto.c @@ -1,9 +1,9 @@ /* zip_crypto_commoncrypto.c -- CommonCrypto wrapper. - Copyright (C) 2018-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_crypto_commoncrypto.h b/core/deps/libzip/lib/zip_crypto_commoncrypto.h index 1eae1b707..01828cc64 100644 --- a/core/deps/libzip/lib/zip_crypto_commoncrypto.h +++ b/core/deps/libzip/lib/zip_crypto_commoncrypto.h @@ -3,7 +3,7 @@ Copyright (C) 2018 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_crypto_gnutls.c b/core/deps/libzip/lib/zip_crypto_gnutls.c index a74bde8f6..1a25aa120 100644 --- a/core/deps/libzip/lib/zip_crypto_gnutls.c +++ b/core/deps/libzip/lib/zip_crypto_gnutls.c @@ -1,9 +1,9 @@ /* zip_crypto_gnutls.c -- GnuTLS wrapper. - Copyright (C) 2018-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -98,15 +98,14 @@ _zip_crypto_aes_free(_zip_crypto_aes_t *aes) { _zip_crypto_hmac_t * _zip_crypto_hmac_new(const zip_uint8_t *secret, zip_uint64_t secret_length, zip_error_t *error) { _zip_crypto_hmac_t *hmac; - int ret; if ((hmac = (_zip_crypto_hmac_t *)malloc(sizeof(*hmac))) == NULL) { zip_error_set(error, ZIP_ER_MEMORY, 0); return NULL; } - if ((ret = gnutls_hmac_init(hmac, GNUTLS_MAC_SHA1, secret, secret_length)) < 0) { - /* TODO: set error */ + if (gnutls_hmac_init(hmac, GNUTLS_MAC_SHA1, secret, secret_length) < 0) { + zip_error_set(error, ZIP_ER_INTERNAL, 0); free(hmac); return NULL; } diff --git a/core/deps/libzip/lib/zip_crypto_gnutls.h b/core/deps/libzip/lib/zip_crypto_gnutls.h index 65cf62ecc..dc8b97a4d 100644 --- a/core/deps/libzip/lib/zip_crypto_gnutls.h +++ b/core/deps/libzip/lib/zip_crypto_gnutls.h @@ -1,9 +1,9 @@ /* zip_crypto_gnutls.h -- definitions for GnuTLS wrapper. - Copyright (C) 2018-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_crypto_mbedtls.c b/core/deps/libzip/lib/zip_crypto_mbedtls.c index 6aeb020c0..84544a821 100644 --- a/core/deps/libzip/lib/zip_crypto_mbedtls.c +++ b/core/deps/libzip/lib/zip_crypto_mbedtls.c @@ -1,9 +1,9 @@ /* zip_crypto_mbedtls.c -- mbed TLS wrapper - Copyright (C) 2018-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -117,6 +117,8 @@ _zip_crypto_pbkdf2(const zip_uint8_t *key, zip_uint64_t key_length, const zip_ui mbedtls_md_context_t sha1_ctx; bool ok = true; +#if MBEDTLS_VERSION_NUMBER < 0x03030000 + mbedtls_md_init(&sha1_ctx); if (mbedtls_md_setup(&sha1_ctx, mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), 1) != 0) { @@ -128,6 +130,13 @@ _zip_crypto_pbkdf2(const zip_uint8_t *key, zip_uint64_t key_length, const zip_ui } mbedtls_md_free(&sha1_ctx); + +#else + + ok = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA1, (const unsigned char *)key, (size_t)key_length, (const unsigned char *)salt, (size_t)salt_length, (unsigned int)iterations, (uint32_t)output_length, (unsigned char *)output) == 0; + +#endif // !defined(MBEDTLS_DEPRECATED_REMOVED) || MBEDTLS_VERSION_NUMBER < 0x03030000 + return ok; } diff --git a/core/deps/libzip/lib/zip_crypto_mbedtls.h b/core/deps/libzip/lib/zip_crypto_mbedtls.h index 0b06e99da..1151fff75 100644 --- a/core/deps/libzip/lib/zip_crypto_mbedtls.h +++ b/core/deps/libzip/lib/zip_crypto_mbedtls.h @@ -1,9 +1,9 @@ /* zip_crypto_mbedtls.h -- definitions for mbedtls wrapper - Copyright (C) 2018-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_crypto_openssl.c b/core/deps/libzip/lib/zip_crypto_openssl.c index 5cbc5ce2c..7f1da10ed 100644 --- a/core/deps/libzip/lib/zip_crypto_openssl.c +++ b/core/deps/libzip/lib/zip_crypto_openssl.c @@ -3,7 +3,7 @@ Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -40,21 +40,70 @@ #include #include -#if OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x02070000fL) -#define USE_OPENSSL_1_0_API +#ifdef USE_OPENSSL_3_API +static _zip_crypto_hmac_t* hmac_new() { + _zip_crypto_hmac_t *hmac = (_zip_crypto_hmac_t*)malloc(sizeof(*hmac)); + if (hmac != NULL) { + hmac->mac = NULL; + hmac->ctx = NULL; + } + return hmac; +} +static void hmac_free(_zip_crypto_hmac_t* hmac) { + if (hmac != NULL) { + if (hmac->ctx != NULL) { + EVP_MAC_CTX_free(hmac->ctx); + } + if (hmac->mac != NULL) { + EVP_MAC_free(hmac->mac); + } + free(hmac); + } +} #endif - _zip_crypto_aes_t * _zip_crypto_aes_new(const zip_uint8_t *key, zip_uint16_t key_size, zip_error_t *error) { _zip_crypto_aes_t *aes; + const EVP_CIPHER* cipher_type; + switch (key_size) { + case 128: + cipher_type = EVP_aes_128_ecb(); + break; + case 192: + cipher_type = EVP_aes_192_ecb(); + break; + case 256: + cipher_type = EVP_aes_256_ecb(); + break; + default: + zip_error_set(error, ZIP_ER_INTERNAL, 0); + return NULL; + } + +#ifdef USE_OPENSSL_1_0_API if ((aes = (_zip_crypto_aes_t *)malloc(sizeof(*aes))) == NULL) { zip_error_set(error, ZIP_ER_MEMORY, 0); return NULL; } + memset(aes, 0, sizeof(*aes)); +#else + if ((aes = EVP_CIPHER_CTX_new()) == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } +#endif - AES_set_encrypt_key(key, key_size, aes); + if (EVP_EncryptInit_ex(aes, cipher_type, NULL, key, NULL) != 1) { +#ifdef USE_OPENSSL_1_0_API + free(aes); +#else + EVP_CIPHER_CTX_free(aes); +#endif + zip_error_set(error, ZIP_ER_INTERNAL, 0); + return NULL; + } return aes; } @@ -65,8 +114,23 @@ _zip_crypto_aes_free(_zip_crypto_aes_t *aes) { return; } +#ifdef USE_OPENSSL_1_0_API + EVP_CIPHER_CTX_cleanup(aes); _zip_crypto_clear(aes, sizeof(*aes)); free(aes); +#else + EVP_CIPHER_CTX_free(aes); +#endif +} + + +bool +_zip_crypto_aes_encrypt_block(_zip_crypto_aes_t *aes, const zip_uint8_t *in, zip_uint8_t *out) { + int len; + if (EVP_EncryptUpdate(aes, out, &len, in, ZIP_CRYPTO_AES_BLOCK_LENGTH) != 1) { + return false; + } + return true; } @@ -79,13 +143,34 @@ _zip_crypto_hmac_new(const zip_uint8_t *secret, zip_uint64_t secret_length, zip_ return NULL; } +#ifdef USE_OPENSSL_3_API + if ((hmac = hmac_new()) == NULL + || (hmac->mac = EVP_MAC_fetch(NULL, "HMAC", "provider=default")) == NULL + || (hmac->ctx = EVP_MAC_CTX_new(hmac->mac)) == NULL) { + hmac_free(hmac); + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + { + OSSL_PARAM params[2]; + params[0] = OSSL_PARAM_construct_utf8_string("digest", "SHA1", 0); + params[1] = OSSL_PARAM_construct_end(); + + if (!EVP_MAC_init(hmac->ctx, (const unsigned char *)secret, secret_length, params)) { + zip_error_set(error, ZIP_ER_INTERNAL, 0); + hmac_free(hmac); + return NULL; + } + } +#else #ifdef USE_OPENSSL_1_0_API if ((hmac = (_zip_crypto_hmac_t *)malloc(sizeof(*hmac))) == NULL) { zip_error_set(error, ZIP_ER_MEMORY, 0); return NULL; } - HMAC_CTX_init(hmac); + HMAC_CTX_init(hmac); #else if ((hmac = HMAC_CTX_new()) == NULL) { zip_error_set(error, ZIP_ER_MEMORY, 0); @@ -102,6 +187,7 @@ _zip_crypto_hmac_new(const zip_uint8_t *secret, zip_uint64_t secret_length, zip_ #endif return NULL; } +#endif return hmac; } @@ -113,7 +199,9 @@ _zip_crypto_hmac_free(_zip_crypto_hmac_t *hmac) { return; } -#ifdef USE_OPENSSL_1_0_API +#if defined(USE_OPENSSL_3_API) + hmac_free(hmac); +#elif defined(USE_OPENSSL_1_0_API) HMAC_CTX_cleanup(hmac); _zip_crypto_clear(hmac, sizeof(*hmac)); free(hmac); @@ -125,9 +213,13 @@ _zip_crypto_hmac_free(_zip_crypto_hmac_t *hmac) { bool _zip_crypto_hmac_output(_zip_crypto_hmac_t *hmac, zip_uint8_t *data) { +#ifdef USE_OPENSSL_3_API + size_t length; + return EVP_MAC_final(hmac->ctx, data, &length, ZIP_CRYPTO_SHA1_LENGTH) == 1 && length == ZIP_CRYPTO_SHA1_LENGTH; +#else unsigned int length; - return HMAC_Final(hmac, data, &length) == 1; +#endif } diff --git a/core/deps/libzip/lib/zip_crypto_openssl.h b/core/deps/libzip/lib/zip_crypto_openssl.h index be0a8ca9f..198a90714 100644 --- a/core/deps/libzip/lib/zip_crypto_openssl.h +++ b/core/deps/libzip/lib/zip_crypto_openssl.h @@ -1,9 +1,9 @@ /* zip_crypto_openssl.h -- definitions for OpenSSL wrapper. - Copyright (C) 2018-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -36,17 +36,34 @@ #define HAVE_SECURE_RANDOM -#include +#include #include -#define _zip_crypto_aes_t AES_KEY +#if OPENSSL_VERSION_NUMBER < 0x1010000fL || (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x02070000fL) +#define USE_OPENSSL_1_0_API +#elif OPENSSL_VERSION_NUMBER < 0x3000000fL +#define USE_OPENSSL_1_1_API +#else +#define USE_OPENSSL_3_API +#endif + +#define _zip_crypto_aes_t EVP_CIPHER_CTX +#ifdef USE_OPENSSL_3_API +struct _zip_crypto_hmac_t { + EVP_MAC *mac; + EVP_MAC_CTX *ctx; +}; +typedef struct _zip_crypto_hmac_t _zip_crypto_hmac_t; +#define _zip_crypto_hmac(hmac, data, length) (EVP_MAC_update((hmac->ctx), (data), (length)) == 1) +#else #define _zip_crypto_hmac_t HMAC_CTX +#define _zip_crypto_hmac(hmac, data, length) (HMAC_Update((hmac), (data), (length)) == 1) +#endif void _zip_crypto_aes_free(_zip_crypto_aes_t *aes); -#define _zip_crypto_aes_encrypt_block(aes, in, out) (AES_encrypt((in), (out), (aes)), true) +bool _zip_crypto_aes_encrypt_block(_zip_crypto_aes_t *aes, const zip_uint8_t *in, zip_uint8_t *out); _zip_crypto_aes_t *_zip_crypto_aes_new(const zip_uint8_t *key, zip_uint16_t key_size, zip_error_t *error); -#define _zip_crypto_hmac(hmac, data, length) (HMAC_Update((hmac), (data), (length)) == 1) void _zip_crypto_hmac_free(_zip_crypto_hmac_t *hmac); _zip_crypto_hmac_t *_zip_crypto_hmac_new(const zip_uint8_t *secret, zip_uint64_t secret_length, zip_error_t *error); bool _zip_crypto_hmac_output(_zip_crypto_hmac_t *hmac, zip_uint8_t *data); diff --git a/core/deps/libzip/lib/zip_crypto_win.c b/core/deps/libzip/lib/zip_crypto_win.c index 6f9a59630..ee3ccc30a 100644 --- a/core/deps/libzip/lib/zip_crypto_win.c +++ b/core/deps/libzip/lib/zip_crypto_win.c @@ -1,9 +1,9 @@ /* zip_crypto_win.c -- Windows Crypto API wrapper. - Copyright (C) 2018-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -254,7 +254,7 @@ pbkdf2(PUCHAR pbPassword, ULONG cbPassword, PUCHAR pbSalt, ULONG cbSalt, DWORD c for (j = 0; j < cIterations; j++) { if (j == 0) { /* construct first input for PRF */ - memcpy(U, pbSalt, cbSalt); + (void)memcpy_s(U, cbSalt, pbSalt, cbSalt); U[cbSalt] = (BYTE)((i & 0xFF000000) >> 24); U[cbSalt + 1] = (BYTE)((i & 0x00FF0000) >> 16); U[cbSalt + 2] = (BYTE)((i & 0x0000FF00) >> 8); @@ -262,7 +262,7 @@ pbkdf2(PUCHAR pbPassword, ULONG cbPassword, PUCHAR pbSalt, ULONG cbSalt, DWORD c dwULen = cbSalt + 4; } else { - memcpy(U, V, DIGEST_SIZE); + (void)memcpy_s(U, DIGEST_SIZE, V, DIGEST_SIZE); dwULen = DIGEST_SIZE; } @@ -274,11 +274,11 @@ pbkdf2(PUCHAR pbPassword, ULONG cbPassword, PUCHAR pbSalt, ULONG cbSalt, DWORD c } if (i != l) { - memcpy(&pbDerivedKey[(i - 1) * DIGEST_SIZE], Ti, DIGEST_SIZE); + (void)memcpy_s(&pbDerivedKey[(i - 1) * DIGEST_SIZE], cbDerivedKey - (i - 1) * DIGEST_SIZE, Ti, DIGEST_SIZE); } else { /* Take only the first r bytes */ - memcpy(&pbDerivedKey[(i - 1) * DIGEST_SIZE], Ti, r); + (void)memcpy_s(&pbDerivedKey[(i - 1) * DIGEST_SIZE], cbDerivedKey - (i - 1) * DIGEST_SIZE, Ti, r); } } diff --git a/core/deps/libzip/lib/zip_crypto_win.h b/core/deps/libzip/lib/zip_crypto_win.h index 9d3689152..a533fe2df 100644 --- a/core/deps/libzip/lib/zip_crypto_win.h +++ b/core/deps/libzip/lib/zip_crypto_win.h @@ -1,9 +1,9 @@ /* zip_crypto_win.h -- Windows Crypto API wrapper. - Copyright (C) 2018-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2018-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_delete.c b/core/deps/libzip/lib/zip_delete.c index 4ff4c1abe..676c16bf2 100644 --- a/core/deps/libzip/lib/zip_delete.c +++ b/core/deps/libzip/lib/zip_delete.c @@ -1,9 +1,9 @@ /* zip_delete.c -- delete file from zip archive - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_dir_add.c b/core/deps/libzip/lib/zip_dir_add.c index da1e4a422..c01081916 100644 --- a/core/deps/libzip/lib/zip_dir_add.c +++ b/core/deps/libzip/lib/zip_dir_add.c @@ -1,9 +1,9 @@ /* zip_dir_add.c -- add directory - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -61,11 +61,11 @@ zip_dir_add(zip_t *za, const char *name, zip_flags_t flags) { len = strlen(name); if (name[len - 1] != '/') { - if ((s = (char *)malloc(len + 2)) == NULL) { + if (len > SIZE_MAX - 2 || (s = (char *)malloc(len + 2)) == NULL) { zip_error_set(&za->error, ZIP_ER_MEMORY, 0); return -1; } - strcpy(s, name); + (void)strncpy_s(s, len + 2, name, len); s[len] = '/'; s[len + 1] = '\0'; } diff --git a/core/deps/libzip/lib/zip_dirent.c b/core/deps/libzip/lib/zip_dirent.c index 5226a6e1f..45a2a6a29 100644 --- a/core/deps/libzip/lib/zip_dirent.c +++ b/core/deps/libzip/lib/zip_dirent.c @@ -1,9 +1,9 @@ /* zip_dirent.c -- read directory entry (local or central), clean dirent - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,6 +37,7 @@ #include #include #include +#include #include "zipint.h" @@ -127,15 +128,21 @@ _zip_cdir_write(zip_t *za, const zip_filelist_t *filelist, zip_uint64_t survivor zip_uint64_t i; bool is_zip64; int ret; + zip_uint32_t cdir_crc; if ((off = zip_source_tell_write(za->src)) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } offset = (zip_uint64_t)off; is_zip64 = false; + if (ZIP_WANT_TORRENTZIP(za)) { + cdir_crc = (zip_uint32_t)crc32(0, NULL, 0); + za->write_crc = &cdir_crc; + } + for (i = 0; i < survivors; i++) { zip_entry_t *entry = za->entry + filelist[i].idx; @@ -145,15 +152,17 @@ _zip_cdir_write(zip_t *za, const zip_filelist_t *filelist, zip_uint64_t survivor is_zip64 = true; } + za->write_crc = NULL; + if ((off = zip_source_tell_write(za->src)) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } size = (zip_uint64_t)off - offset; - if (offset > ZIP_UINT32_MAX || survivors > ZIP_UINT16_MAX) + if (offset > ZIP_UINT32_MAX || survivors > ZIP_UINT16_MAX) { is_zip64 = true; - + } if ((buffer = _zip_buffer_new(buf, sizeof(buf))) == NULL) { zip_error_set(&za->error, ZIP_ER_MEMORY, 0); @@ -186,7 +195,13 @@ _zip_cdir_write(zip_t *za, const zip_filelist_t *filelist, zip_uint64_t survivor comment = za->comment_changed ? za->comment_changes : za->comment_orig; - _zip_buffer_put_16(buffer, (zip_uint16_t)(comment ? comment->length : 0)); + if (ZIP_WANT_TORRENTZIP(za)) { + _zip_buffer_put_16(buffer, TORRENTZIP_SIGNATURE_LENGTH + TORRENTZIP_CRC_LENGTH); + } + else { + _zip_buffer_put_16(buffer, (zip_uint16_t)(comment ? comment->length : 0)); + } + if (!_zip_buffer_ok(buffer)) { zip_error_set(&za->error, ZIP_ER_INTERNAL, 0); @@ -201,7 +216,15 @@ _zip_cdir_write(zip_t *za, const zip_filelist_t *filelist, zip_uint64_t survivor _zip_buffer_free(buffer); - if (comment) { + if (ZIP_WANT_TORRENTZIP(za)) { + char torrentzip_comment[TORRENTZIP_SIGNATURE_LENGTH + TORRENTZIP_CRC_LENGTH + 1]; + snprintf(torrentzip_comment, sizeof(torrentzip_comment), TORRENTZIP_SIGNATURE "%08X", cdir_crc); + + if (_zip_write(za, torrentzip_comment, strlen(torrentzip_comment)) < 0) { + return -1; + } + } + else if (comment != NULL) { if (_zip_write(za, comment->raw, comment->length) < 0) { return -1; } @@ -219,7 +242,7 @@ _zip_dirent_clone(const zip_dirent_t *sde) { return NULL; if (sde) - memcpy(tde, sde, sizeof(*sde)); + (void)memcpy_s(tde, sizeof(*tde), sde, sizeof(*sde)); else _zip_dirent_init(tde); @@ -421,7 +444,7 @@ _zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, boo if (from_buffer) { if (_zip_buffer_left(buffer) < variable_size) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_VARIABLE_SIZE_OVERFLOW); return -1; } } @@ -437,7 +460,7 @@ _zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, boo zde->filename = _zip_read_string(buffer, src, filename_len, 1, error); if (!zde->filename) { if (zip_error_code_zip(error) == ZIP_ER_EOF) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_VARIABLE_SIZE_OVERFLOW); } if (!from_buffer) { _zip_buffer_free(buffer); @@ -447,7 +470,7 @@ _zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, boo if (zde->bitflags & ZIP_GPBF_ENCODING_UTF_8) { if (_zip_guess_encoding(zde->filename, ZIP_ENCODING_UTF8_KNOWN) == ZIP_ENCODING_ERROR) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_INVALID_UTF8_IN_FILENAME); if (!from_buffer) { _zip_buffer_free(buffer); } @@ -487,7 +510,7 @@ _zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, boo } if (zde->bitflags & ZIP_GPBF_ENCODING_UTF_8) { if (_zip_guess_encoding(zde->comment, ZIP_ENCODING_UTF8_KNOWN) == ZIP_ENCODING_ERROR) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_INVALID_UTF8_IN_COMMENT); if (!from_buffer) { _zip_buffer_free(buffer); } @@ -503,76 +526,18 @@ _zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, boo if (zde->uncomp_size == ZIP_UINT32_MAX || zde->comp_size == ZIP_UINT32_MAX || zde->offset == ZIP_UINT32_MAX) { zip_uint16_t got_len; - zip_buffer_t *ef_buffer; const zip_uint8_t *ef = _zip_ef_get_by_id(zde->extra_fields, &got_len, ZIP_EF_ZIP64, 0, local ? ZIP_EF_LOCAL : ZIP_EF_CENTRAL, error); - /* TODO: if got_len == 0 && !ZIP64_EOCD: no error, 0xffffffff is valid value */ - if (ef == NULL) { - if (!from_buffer) { - _zip_buffer_free(buffer); - } - return -1; - } - - if ((ef_buffer = _zip_buffer_new((zip_uint8_t *)ef, got_len)) == NULL) { - zip_error_set(error, ZIP_ER_MEMORY, 0); - if (!from_buffer) { - _zip_buffer_free(buffer); - } - return -1; - } - - if (zde->uncomp_size == ZIP_UINT32_MAX) { - zde->uncomp_size = _zip_buffer_get_64(ef_buffer); - } - else if (local) { - /* From appnote.txt: This entry in the Local header MUST - include BOTH original and compressed file size fields. */ - (void)_zip_buffer_skip(ef_buffer, 8); /* error is caught by _zip_buffer_eof() call */ - } - if (zde->comp_size == ZIP_UINT32_MAX) { - zde->comp_size = _zip_buffer_get_64(ef_buffer); - } - if (!local) { - if (zde->offset == ZIP_UINT32_MAX) { - zde->offset = _zip_buffer_get_64(ef_buffer); - } - if (zde->disk_number == ZIP_UINT16_MAX) { - zde->disk_number = _zip_buffer_get_32(ef_buffer); - } - } - - if (!_zip_buffer_eof(ef_buffer)) { - /* accept additional fields if values match */ - bool ok = true; - switch (got_len) { - case 28: - _zip_buffer_set_offset(ef_buffer, 24); - if (zde->disk_number != _zip_buffer_get_32(ef_buffer)) { - ok = false; - } - /* fallthrough */ - case 24: - _zip_buffer_set_offset(ef_buffer, 0); - if ((zde->uncomp_size != _zip_buffer_get_64(ef_buffer)) || (zde->comp_size != _zip_buffer_get_64(ef_buffer)) || (zde->offset != _zip_buffer_get_64(ef_buffer))) { - ok = false; - } - break; - - default: - ok = false; - } - if (!ok) { - zip_error_set(error, ZIP_ER_INCONS, 0); - _zip_buffer_free(ef_buffer); + if (ef != NULL) { + if (!zip_dirent_process_ef_zip64(zde, ef, got_len, local, error)) { if (!from_buffer) { _zip_buffer_free(buffer); } return -1; } } - _zip_buffer_free(ef_buffer); } + if (!_zip_buffer_ok(buffer)) { zip_error_set(error, ZIP_ER_INTERNAL, 0); if (!from_buffer) { @@ -599,6 +564,65 @@ _zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, boo return (zip_int64_t)size + (zip_int64_t)variable_size; } +bool zip_dirent_process_ef_zip64(zip_dirent_t* zde, const zip_uint8_t* ef, zip_uint64_t got_len, bool local, zip_error_t* error) { + zip_buffer_t *ef_buffer; + + if ((ef_buffer = _zip_buffer_new((zip_uint8_t *)ef, got_len)) == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return false; + } + + if (zde->uncomp_size == ZIP_UINT32_MAX) { + zde->uncomp_size = _zip_buffer_get_64(ef_buffer); + } + else if (local) { + /* From appnote.txt: This entry in the Local header MUST + include BOTH original and compressed file size fields. */ + (void)_zip_buffer_skip(ef_buffer, 8); /* error is caught by _zip_buffer_eof() call */ + } + if (zde->comp_size == ZIP_UINT32_MAX) { + zde->comp_size = _zip_buffer_get_64(ef_buffer); + } + if (!local) { + if (zde->offset == ZIP_UINT32_MAX) { + zde->offset = _zip_buffer_get_64(ef_buffer); + } + if (zde->disk_number == ZIP_UINT16_MAX) { + zde->disk_number = _zip_buffer_get_32(ef_buffer); + } + } + + if (!_zip_buffer_eof(ef_buffer)) { + /* accept additional fields if values match */ + bool ok = true; + switch (got_len) { + case 28: + _zip_buffer_set_offset(ef_buffer, 24); + if (zde->disk_number != _zip_buffer_get_32(ef_buffer)) { + ok = false; + } + /* fallthrough */ + case 24: + _zip_buffer_set_offset(ef_buffer, 0); + if ((zde->uncomp_size != _zip_buffer_get_64(ef_buffer)) || (zde->comp_size != _zip_buffer_get_64(ef_buffer)) || (zde->offset != _zip_buffer_get_64(ef_buffer))) { + ok = false; + } + break; + + default: + ok = false; + } + if (!ok) { + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_INVALID_ZIP64_EF); + _zip_buffer_free(ef_buffer); + return false; + } + } + _zip_buffer_free(ef_buffer); + + return true; +} + static zip_string_t * _zip_dirent_process_ef_utf_8(const zip_dirent_t *de, zip_uint16_t id, zip_string_t *str) { @@ -651,7 +675,7 @@ _zip_dirent_process_winzip_aes(zip_dirent_t *de, zip_error_t *error) { ef = _zip_ef_get_by_id(de->extra_fields, &ef_len, ZIP_EF_WINZIP_AES, 0, ZIP_EF_BOTH, NULL); if (ef == NULL || ef_len < 7) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_INVALID_WINZIPAES_EF); return false; } @@ -664,19 +688,18 @@ _zip_dirent_process_winzip_aes(zip_dirent_t *de, zip_error_t *error) { crc_valid = true; switch (_zip_buffer_get_16(buffer)) { - case 1: - break; + case 1: + break; - case 2: - if (de->uncomp_size < 20 /* TODO: constant */) { + case 2: crc_valid = false; - } - break; - - default: - zip_error_set(error, ZIP_ER_ENCRNOTSUPP, 0); - _zip_buffer_free(buffer); - return false; + /* TODO: When checking consistency, check that crc is 0. */ + break; + + default: + zip_error_set(error, ZIP_ER_ENCRNOTSUPP, 0); + _zip_buffer_free(buffer); + return false; } /* vendor */ @@ -704,7 +727,7 @@ _zip_dirent_process_winzip_aes(zip_dirent_t *de, zip_error_t *error) { } if (ef_len != 7) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_INVALID_WINZIPAES_EF); _zip_buffer_free(buffer); return false; } @@ -729,7 +752,7 @@ _zip_dirent_size(zip_source_t *src, zip_uint16_t flags, zip_error_t *error) { size = local ? LENTRYSIZE : CDENTRYSIZE; if (zip_source_seek(src, local ? 26 : 28, SEEK_CUR) < 0) { - _zip_error_set_from_source(error, src); + zip_error_set_from_source(error, src); return -1; } @@ -902,7 +925,13 @@ _zip_dirent_write(zip_t *za, zip_dirent_t *de, zip_flags_t flags) { _zip_buffer_put_16(buffer, (zip_uint16_t)de->comp_method); } - _zip_u2d_time(de->last_mod, &dostime, &dosdate); + if (ZIP_WANT_TORRENTZIP(za)) { + dostime = 0xbc00; + dosdate = 0x2198; + } + else { + _zip_u2d_time(de->last_mod, &dostime, &dosdate); + } _zip_buffer_put_16(buffer, dostime); _zip_buffer_put_16(buffer, dosdate); @@ -938,12 +967,15 @@ _zip_dirent_write(zip_t *za, zip_dirent_t *de, zip_flags_t flags) { } _zip_buffer_put_16(buffer, _zip_string_length(de->filename)); - /* TODO: check for overflow */ - ef_total_size = (zip_uint32_t)_zip_ef_size(de->extra_fields, flags) + (zip_uint32_t)_zip_ef_size(ef, ZIP_EF_BOTH); + ef_total_size = (zip_uint32_t)_zip_ef_size(ef, ZIP_EF_BOTH); + if (!ZIP_WANT_TORRENTZIP(za)) { + /* TODO: check for overflow */ + ef_total_size += (zip_uint32_t)_zip_ef_size(de->extra_fields, flags); + } _zip_buffer_put_16(buffer, (zip_uint16_t)ef_total_size); if ((flags & ZIP_FL_LOCAL) == 0) { - _zip_buffer_put_16(buffer, _zip_string_length(de->comment)); + _zip_buffer_put_16(buffer, ZIP_WANT_TORRENTZIP(za) ? 0 : _zip_string_length(de->comment)); _zip_buffer_put_16(buffer, (zip_uint16_t)de->disk_number); _zip_buffer_put_16(buffer, de->int_attrib); _zip_buffer_put_32(buffer, de->ext_attrib); @@ -982,13 +1014,13 @@ _zip_dirent_write(zip_t *za, zip_dirent_t *de, zip_flags_t flags) { } } _zip_ef_free(ef); - if (de->extra_fields) { + if (de->extra_fields && !ZIP_WANT_TORRENTZIP(za)) { if (_zip_ef_write(za, de->extra_fields, flags) < 0) { return -1; } } - if ((flags & ZIP_FL_LOCAL) == 0) { + if ((flags & ZIP_FL_LOCAL) == 0 && !ZIP_WANT_TORRENTZIP(za)) { if (de->comment) { if (_zip_string_write(za, de->comment) < 0) { return -1; @@ -1090,15 +1122,10 @@ _zip_get_dirent(zip_t *za, zip_uint64_t idx, zip_flags_t flags, zip_error_t *err void _zip_u2d_time(time_t intime, zip_uint16_t *dtime, zip_uint16_t *ddate) { struct tm *tpm; - -#ifdef HAVE_LOCALTIME_R struct tm tm; - tpm = localtime_r(&intime, &tm); -#else - tpm = localtime(&intime); -#endif + tpm = zip_localtime(&intime, &tm); if (tpm == NULL) { - /* if localtime() fails, return an arbitrary date (1980-01-01 00:00:00) */ + /* if localtime fails, return an arbitrary date (1980-01-01 00:00:00) */ *ddate = (1 << 5) + 1; *dtime = 0; return; @@ -1109,8 +1136,6 @@ _zip_u2d_time(time_t intime, zip_uint16_t *dtime, zip_uint16_t *ddate) { *ddate = (zip_uint16_t)(((tpm->tm_year + 1900 - 1980) << 9) + ((tpm->tm_mon + 1) << 5) + tpm->tm_mday); *dtime = (zip_uint16_t)(((tpm->tm_hour) << 11) + ((tpm->tm_min) << 5) + ((tpm->tm_sec) >> 1)); - - return; } @@ -1161,3 +1186,22 @@ _zip_dirent_apply_attributes(zip_dirent_t *de, zip_file_attributes_t *attributes de->version_madeby = (de->version_madeby & 0xff) | (zip_uint16_t)(attributes->host_system << 8); } } + + +/* _zip_dirent_torrent_normalize(de); + Set values suitable for torrentzip. +*/ + +void zip_dirent_torrentzip_normalize(zip_dirent_t *de) { + de->version_madeby = 0; + de->version_needed = 20; /* 2.0 */ + de->bitflags = 2; /* maximum compression */ + de->comp_method = ZIP_CM_DEFLATE; + de->compression_level = TORRENTZIP_COMPRESSION_FLAGS; + de->disk_number = 0; + de->int_attrib = 0; + de->ext_attrib = 0; + + /* last_mod, extra_fields, and comment are normalized in zip_dirent_write() directly */ + +} diff --git a/core/deps/libzip/lib/zip_discard.c b/core/deps/libzip/lib/zip_discard.c index a29a2884b..d1dc4f8b0 100644 --- a/core/deps/libzip/lib/zip_discard.c +++ b/core/deps/libzip/lib/zip_discard.c @@ -1,9 +1,9 @@ /* zip_discard.c -- discard and free struct zip - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_entry.c b/core/deps/libzip/lib/zip_entry.c index 837b59c13..35a36e4a1 100644 --- a/core/deps/libzip/lib/zip_entry.c +++ b/core/deps/libzip/lib/zip_entry.c @@ -1,9 +1,9 @@ /* zip_entry.c -- struct zip_entry helper functions - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_err_str.c b/core/deps/libzip/lib/zip_err_str.c deleted file mode 100644 index de141ffe7..000000000 --- a/core/deps/libzip/lib/zip_err_str.c +++ /dev/null @@ -1,84 +0,0 @@ -/* - This file was generated automatically by CMake - from zip.h; make changes there. -*/ - -#include "zipint.h" - -const char * const _zip_err_str[] = { - "No error", - "Multi-disk zip archives not supported", - "Renaming temporary file failed", - "Closing zip archive failed", - "Seek error", - "Read error", - "Write error", - "CRC error", - "Containing zip archive was closed", - "No such file", - "File already exists", - "Can't open file", - "Failure to create temporary file", - "Zlib error", - "Malloc failure", - "Entry has been changed", - "Compression method not supported", - "Premature end of file", - "Invalid argument", - "Not a zip archive", - "Internal error", - "Zip archive inconsistent", - "Can't remove file", - "Entry has been deleted", - "Encryption method not supported", - "Read-only archive", - "No password provided", - "Wrong password provided", - "Operation not supported", - "Resource still in use", - "Tell error", - "Compressed data invalid", - "Operation cancelled", -}; - -const int _zip_nerr_str = sizeof(_zip_err_str)/sizeof(_zip_err_str[0]); - -#define N ZIP_ET_NONE -#define S ZIP_ET_SYS -#define Z ZIP_ET_ZLIB - -const int _zip_err_type[] = { - N, - N, - S, - S, - S, - S, - S, - N, - N, - N, - N, - S, - S, - Z, - N, - N, - N, - N, - N, - N, - N, - N, - S, - N, - N, - N, - N, - N, - N, - N, - S, - N, - N, -}; diff --git a/core/deps/libzip/lib/zip_error.c b/core/deps/libzip/lib/zip_error.c index 9381d5bf8..c498e0860 100644 --- a/core/deps/libzip/lib/zip_error.c +++ b/core/deps/libzip/lib/zip_error.c @@ -1,9 +1,9 @@ /* zip_error.c -- zip_error_t helper functions - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -67,23 +67,24 @@ zip_error_init_with_code(zip_error_t *error, int ze) { zip_error_init(error); error->zip_err = ze; switch (zip_error_system_type(error)) { - case ZIP_ET_SYS: - error->sys_err = errno; - break; - - default: - error->sys_err = 0; - break; + case ZIP_ET_SYS: + case ZIP_ET_LIBZIP: + error->sys_err = errno; + break; + + default: + error->sys_err = 0; + break; } } ZIP_EXTERN int zip_error_system_type(const zip_error_t *error) { - if (error->zip_err < 0 || error->zip_err >= _zip_nerr_str) + if (error->zip_err < 0 || error->zip_err >= _zip_err_str_count) return ZIP_ET_NONE; - return _zip_err_type[error->zip_err]; + return _zip_err_str[error->zip_err].type; } @@ -131,7 +132,12 @@ zip_error_set(zip_error_t *err, int ze, int se) { void -_zip_error_set_from_source(zip_error_t *err, zip_source_t *src) { +zip_error_set_from_source(zip_error_t *err, zip_source_t *src) { + if (src == NULL) { + zip_error_set(err, ZIP_ER_INVAL, 0); + return; + } + _zip_error_copy(err, zip_source_error(src)); } diff --git a/core/deps/libzip/lib/zip_error_clear.c b/core/deps/libzip/lib/zip_error_clear.c index 9bcd506ec..94ff5062d 100644 --- a/core/deps/libzip/lib/zip_error_clear.c +++ b/core/deps/libzip/lib/zip_error_clear.c @@ -1,9 +1,9 @@ /* zip_error_clear.c -- clear zip error - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_error_get.c b/core/deps/libzip/lib/zip_error_get.c index 9d0ec03ff..c0418f0d0 100644 --- a/core/deps/libzip/lib/zip_error_get.c +++ b/core/deps/libzip/lib/zip_error_get.c @@ -1,9 +1,9 @@ /* zip_error_get.c -- get zip error - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_error_get_sys_type.c b/core/deps/libzip/lib/zip_error_get_sys_type.c index 3c9998c57..a22ffb036 100644 --- a/core/deps/libzip/lib/zip_error_get_sys_type.c +++ b/core/deps/libzip/lib/zip_error_get_sys_type.c @@ -1,9 +1,9 @@ /* zip_error_get_sys_type.c -- return type of system error code - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,8 +37,9 @@ ZIP_EXTERN int zip_error_get_sys_type(int ze) { - if (ze < 0 || ze >= _zip_nerr_str) + if (ze < 0 || ze >= _zip_err_str_count) { return 0; + } - return _zip_err_type[ze]; + return _zip_err_str[ze].type; } diff --git a/core/deps/libzip/lib/zip_error_strerror.c b/core/deps/libzip/lib/zip_error_strerror.c index d20b3b2fa..fe04cbb4f 100644 --- a/core/deps/libzip/lib/zip_error_strerror.c +++ b/core/deps/libzip/lib/zip_error_strerror.c @@ -1,9 +1,9 @@ /* zip_error_sterror.c -- get string representation of struct zip_error - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -39,45 +39,90 @@ #include "zipint.h" - ZIP_EXTERN const char * zip_error_strerror(zip_error_t *err) { - const char *zs, *ss; - char buf[128], *s; + const char *zip_error_string, *system_error_string; + char *s; + char *system_error_buffer = NULL; zip_error_fini(err); - if (err->zip_err < 0 || err->zip_err >= _zip_nerr_str) { - sprintf(buf, "Unknown error %d", err->zip_err); - zs = NULL; - ss = buf; + if (err->zip_err < 0 || err->zip_err >= _zip_err_str_count) { + system_error_buffer = (char *)malloc(128); + snprintf_s(system_error_buffer, 128, "Unknown error %d", err->zip_err); + system_error_buffer[128 - 1] = '\0'; /* make sure string is NUL-terminated */ + zip_error_string = NULL; + system_error_string = system_error_buffer; } else { - zs = _zip_err_str[err->zip_err]; + zip_error_string = _zip_err_str[err->zip_err].description; - switch (_zip_err_type[err->zip_err]) { - case ZIP_ET_SYS: - ss = strerror(err->sys_err); - break; - - case ZIP_ET_ZLIB: - ss = zError(err->sys_err); - break; - - default: - ss = NULL; + switch (_zip_err_str[err->zip_err].type) { + case ZIP_ET_SYS: { + size_t len = strerrorlen_s(err->sys_err) + 1; + system_error_buffer = malloc(len); + strerror_s(system_error_buffer, len, err->sys_err); + system_error_string = system_error_buffer; + break; + } + + case ZIP_ET_ZLIB: + system_error_string = zError(err->sys_err); + break; + + case ZIP_ET_LIBZIP: { + zip_uint8_t error = GET_ERROR_FROM_DETAIL(err->sys_err); + int index = GET_INDEX_FROM_DETAIL(err->sys_err); + + if (error == 0) { + system_error_string = NULL; + } + else if (error >= _zip_err_details_count) { + system_error_buffer = (char *)malloc(128); + snprintf_s(system_error_buffer, 128, "invalid detail error %u", error); + system_error_buffer[128 - 1] = '\0'; /* make sure string is NUL-terminated */ + system_error_string = system_error_buffer; + } + else if (_zip_err_details[error].type == ZIP_DETAIL_ET_ENTRY && index < MAX_DETAIL_INDEX) { + system_error_buffer = (char *)malloc(128); + snprintf_s(system_error_buffer, 128, "entry %d: %s", index, _zip_err_details[error].description); + system_error_buffer[128 - 1] = '\0'; /* make sure string is NUL-terminated */ + system_error_string = system_error_buffer; + } + else { + system_error_string = _zip_err_details[error].description; + } + break; + } + + default: + system_error_string = NULL; } } - if (ss == NULL) - return zs; + if (system_error_string == NULL) { + free(system_error_buffer); + return zip_error_string; + } else { - if ((s = (char *)malloc(strlen(ss) + (zs ? strlen(zs) + 2 : 0) + 1)) == NULL) - return _zip_err_str[ZIP_ER_MEMORY]; + size_t length = strlen(system_error_string); + if (zip_error_string) { + size_t length_error = strlen(zip_error_string); + if (length + length_error + 2 < length) { + free(system_error_buffer); + return _zip_err_str[ZIP_ER_MEMORY].description; + } + length += length_error + 2; + } + if (length == SIZE_MAX || (s = (char *)malloc(length + 1)) == NULL) { + free(system_error_buffer); + return _zip_err_str[ZIP_ER_MEMORY].description; + } - sprintf(s, "%s%s%s", (zs ? zs : ""), (zs ? ": " : ""), ss); + snprintf_s(s, length + 1, "%s%s%s", (zip_error_string ? zip_error_string : ""), (zip_error_string ? ": " : ""), system_error_string); err->str = s; + free(system_error_buffer); return s; } } diff --git a/core/deps/libzip/lib/zip_error_to_str.c b/core/deps/libzip/lib/zip_error_to_str.c index d315b1901..b60b78813 100644 --- a/core/deps/libzip/lib/zip_error_to_str.c +++ b/core/deps/libzip/lib/zip_error_to_str.c @@ -1,9 +1,9 @@ /* zip_error_to_str.c -- get string representation of zip error code - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -34,7 +34,6 @@ #include #include -#include #define _ZIP_COMPILING_DEPRECATED #include "zipint.h" @@ -42,25 +41,18 @@ ZIP_EXTERN int zip_error_to_str(char *buf, zip_uint64_t len, int ze, int se) { - const char *zs, *ss; + zip_error_t error; + const char *error_string; + int ret; - if (ze < 0 || ze >= _zip_nerr_str) - return snprintf(buf, len, "Unknown error %d", ze); + zip_error_init(&error); + zip_error_set(&error, ze, se); - zs = _zip_err_str[ze]; + error_string = zip_error_strerror(&error); - switch (_zip_err_type[ze]) { - case ZIP_ET_SYS: - ss = strerror(se); - break; + ret = snprintf_s(buf, ZIP_MIN(len, SIZE_MAX), error_string, strlen(error_string)); - case ZIP_ET_ZLIB: - ss = zError(se); - break; + zip_error_fini(&error); - default: - ss = NULL; - } - - return snprintf(buf, len, "%s%s%s", zs, (ss ? ": " : ""), (ss ? ss : "")); + return ret; } diff --git a/core/deps/libzip/lib/zip_extra_field.c b/core/deps/libzip/lib/zip_extra_field.c index 7424d8831..7aed12adb 100644 --- a/core/deps/libzip/lib/zip_extra_field.c +++ b/core/deps/libzip/lib/zip_extra_field.c @@ -1,9 +1,9 @@ /* zip_extra_field.c -- manipulate extra fields - Copyright (C) 2012-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2012-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -219,7 +219,7 @@ _zip_ef_parse(const zip_uint8_t *data, zip_uint16_t len, zip_flags_t flags, zip_ ef_data = _zip_buffer_get(buffer, flen); if (ef_data == NULL) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_INVALID_EF_LENGTH); _zip_buffer_free(buffer); _zip_ef_free(ef_head); return false; @@ -243,11 +243,12 @@ _zip_ef_parse(const zip_uint8_t *data, zip_uint16_t len, zip_flags_t flags, zip_ if (!_zip_buffer_eof(buffer)) { /* Android APK files align stored file data with padding in extra fields; ignore. */ /* see https://android.googlesource.com/platform/build/+/master/tools/zipalign/ZipAlign.cpp */ + /* buffer is at most 64k long, so this can't overflow. */ size_t glen = _zip_buffer_left(buffer); zip_uint8_t *garbage; garbage = _zip_buffer_get(buffer, glen); - if (glen >= 4 || garbage == NULL || memcmp(garbage, "\0\0\0", glen) != 0) { - zip_error_set(error, ZIP_ER_INCONS, 0); + if (glen >= 4 || garbage == NULL || memcmp(garbage, "\0\0\0", (size_t)glen) != 0) { + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_EF_TRAILING_GARBAGE); _zip_buffer_free(buffer); _zip_ef_free(ef_head); return false; @@ -370,7 +371,7 @@ _zip_read_local_ef(zip_t *za, zip_uint64_t idx) { } if (zip_source_seek(za->src, (zip_int64_t)(e->orig->offset + 26), SEEK_SET) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } diff --git a/core/deps/libzip/lib/zip_extra_field_api.c b/core/deps/libzip/lib/zip_extra_field_api.c index cf2b0e7f1..560c71bb2 100644 --- a/core/deps/libzip/lib/zip_extra_field_api.c +++ b/core/deps/libzip/lib/zip_extra_field_api.c @@ -1,9 +1,9 @@ /* zip_extra_field_api.c -- public extra fields API functions - Copyright (C) 2012-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2012-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -56,6 +56,10 @@ zip_file_extra_field_delete(zip_t *za, zip_uint64_t idx, zip_uint16_t ef_idx, zi zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } if (_zip_file_extra_field_prepare_for_change(za, idx) < 0) return -1; @@ -88,6 +92,10 @@ zip_file_extra_field_delete_by_id(zip_t *za, zip_uint64_t idx, zip_uint16_t ef_i zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } if (_zip_file_extra_field_prepare_for_change(za, idx) < 0) return -1; @@ -236,6 +244,10 @@ zip_file_extra_field_set(zip_t *za, zip_uint64_t idx, zip_uint16_t ef_id, zip_ui zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } if (ZIP_EF_IS_INTERNAL(ef_id)) { zip_error_set(&za->error, ZIP_ER_INVAL, 0); diff --git a/core/deps/libzip/lib/zip_fclose.c b/core/deps/libzip/lib/zip_fclose.c index 4d6fc59ce..b820d98bd 100644 --- a/core/deps/libzip/lib/zip_fclose.c +++ b/core/deps/libzip/lib/zip_fclose.c @@ -1,9 +1,9 @@ /* zip_fclose.c -- close file in zip archive - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_fdopen.c b/core/deps/libzip/lib/zip_fdopen.c index 14f243fbb..e72c55dca 100644 --- a/core/deps/libzip/lib/zip_fdopen.c +++ b/core/deps/libzip/lib/zip_fdopen.c @@ -1,9 +1,9 @@ /* zip_fdopen.c -- open read-only archive from file descriptor - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -51,6 +51,10 @@ zip_fdopen(int fd_orig, int _flags, int *zep) { return NULL; } +#ifndef ENABLE_FDOPEN + _zip_set_open_error(zep, NULL, ZIP_ER_OPNOTSUPP); + return NULL; +#else /* We dup() here to avoid messing with the passed in fd. We could not restore it to the original state in case of error. */ @@ -83,4 +87,5 @@ zip_fdopen(int fd_orig, int _flags, int *zep) { zip_error_fini(&error); close(fd_orig); return za; +#endif } diff --git a/core/deps/libzip/lib/zip_file_add.c b/core/deps/libzip/lib/zip_file_add.c index 40386c095..c2c41e156 100644 --- a/core/deps/libzip/lib/zip_file_add.c +++ b/core/deps/libzip/lib/zip_file_add.c @@ -1,9 +1,9 @@ /* zip_file_add.c -- add file via callback function - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_file_error_clear.c b/core/deps/libzip/lib/zip_file_error_clear.c index 863dae762..a10bff80c 100644 --- a/core/deps/libzip/lib/zip_file_error_clear.c +++ b/core/deps/libzip/lib/zip_file_error_clear.c @@ -1,9 +1,9 @@ /* zip_file_error_clear.c -- clear zip file error - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_file_error_get.c b/core/deps/libzip/lib/zip_file_error_get.c index a47797646..b93117bb9 100644 --- a/core/deps/libzip/lib/zip_file_error_get.c +++ b/core/deps/libzip/lib/zip_file_error_get.c @@ -1,9 +1,9 @@ /* zip_file_error_get.c -- get zip file error - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_file_get_comment.c b/core/deps/libzip/lib/zip_file_get_comment.c index 746186a71..fa998f02e 100644 --- a/core/deps/libzip/lib/zip_file_get_comment.c +++ b/core/deps/libzip/lib/zip_file_get_comment.c @@ -1,9 +1,9 @@ /* zip_file_get_comment.c -- get file comment - Copyright (C) 2006-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2006-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_file_get_external_attributes.c b/core/deps/libzip/lib/zip_file_get_external_attributes.c index 6d4e0e490..a79bb3ed2 100644 --- a/core/deps/libzip/lib/zip_file_get_external_attributes.c +++ b/core/deps/libzip/lib/zip_file_get_external_attributes.c @@ -1,9 +1,9 @@ /* zip_file_get_external_attributes.c -- get opsys/external attributes - Copyright (C) 2013-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2013-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_file_get_offset.c b/core/deps/libzip/lib/zip_file_get_offset.c index eb9b0178c..72f4880e7 100644 --- a/core/deps/libzip/lib/zip_file_get_offset.c +++ b/core/deps/libzip/lib/zip_file_get_offset.c @@ -1,9 +1,9 @@ /* zip_file_get_offset.c -- get offset of file data in archive. - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -57,7 +57,7 @@ _zip_file_get_offset(const zip_t *za, zip_uint64_t idx, zip_error_t *error) { offset = za->entry[idx].orig->offset; if (zip_source_seek(za->src, (zip_int64_t)offset, SEEK_SET) < 0) { - _zip_error_set_from_source(error, za->src); + zip_error_set_from_source(error, za->src); return 0; } @@ -93,11 +93,11 @@ _zip_file_get_end(const zip_t *za, zip_uint64_t index, zip_error_t *error) { if (entry->bitflags & ZIP_GPBF_DATA_DESCRIPTOR) { zip_uint8_t buf[4]; if (zip_source_seek(za->src, (zip_int64_t)offset, SEEK_SET) < 0) { - _zip_error_set_from_source(error, za->src); + zip_error_set_from_source(error, za->src); return 0; } if (zip_source_read(za->src, buf, 4) != 4) { - _zip_error_set_from_source(error, za->src); + zip_error_set_from_source(error, za->src); return 0; } if (memcmp(buf, DATADES_MAGIC, 4) == 0) { diff --git a/core/deps/libzip/lib/zip_file_rename.c b/core/deps/libzip/lib/zip_file_rename.c index a830b6aa8..9ac25814a 100644 --- a/core/deps/libzip/lib/zip_file_rename.c +++ b/core/deps/libzip/lib/zip_file_rename.c @@ -1,9 +1,9 @@ /* zip_file_rename.c -- rename file in zip archive - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_file_replace.c b/core/deps/libzip/lib/zip_file_replace.c index c824d7fba..4262d453e 100644 --- a/core/deps/libzip/lib/zip_file_replace.c +++ b/core/deps/libzip/lib/zip_file_replace.c @@ -1,9 +1,9 @@ /* zip_file_replace.c -- replace file via callback function - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_file_set_comment.c b/core/deps/libzip/lib/zip_file_set_comment.c index 8c76f25ac..570f8e821 100644 --- a/core/deps/libzip/lib/zip_file_set_comment.c +++ b/core/deps/libzip/lib/zip_file_set_comment.c @@ -1,9 +1,9 @@ /* zip_file_set_comment.c -- set comment for file in archive - Copyright (C) 2006-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2006-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -50,6 +50,10 @@ zip_file_set_comment(zip_t *za, zip_uint64_t idx, const char *comment, zip_uint1 zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } if (len > 0 && comment == NULL) { zip_error_set(&za->error, ZIP_ER_INVAL, 0); diff --git a/core/deps/libzip/lib/zip_file_set_encryption.c b/core/deps/libzip/lib/zip_file_set_encryption.c index 6025e4a82..1cdcd4ab6 100644 --- a/core/deps/libzip/lib/zip_file_set_encryption.c +++ b/core/deps/libzip/lib/zip_file_set_encryption.c @@ -1,9 +1,9 @@ /* zip_file_set_encryption.c -- set encryption for file in archive - Copyright (C) 2016-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2016-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -51,6 +51,10 @@ zip_file_set_encryption(zip_t *za, zip_uint64_t idx, zip_uint16_t method, const zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } if (method != ZIP_EM_NONE && _zip_get_encryption_implementation(method, ZIP_CODEC_ENCODE) == NULL) { zip_error_set(&za->error, ZIP_ER_ENCRNOTSUPP, 0); diff --git a/core/deps/libzip/lib/zip_file_set_external_attributes.c b/core/deps/libzip/lib/zip_file_set_external_attributes.c index 0662f60d2..2e0429b8c 100644 --- a/core/deps/libzip/lib/zip_file_set_external_attributes.c +++ b/core/deps/libzip/lib/zip_file_set_external_attributes.c @@ -1,9 +1,9 @@ /* zip_file_set_external_attributes.c -- set external attributes for entry - Copyright (C) 2013-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2013-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -47,6 +47,10 @@ zip_file_set_external_attributes(zip_t *za, zip_uint64_t idx, zip_flags_t flags, zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } e = za->entry + idx; diff --git a/core/deps/libzip/lib/zip_file_set_mtime.c b/core/deps/libzip/lib/zip_file_set_mtime.c index 2753d595e..4126f5a16 100644 --- a/core/deps/libzip/lib/zip_file_set_mtime.c +++ b/core/deps/libzip/lib/zip_file_set_mtime.c @@ -1,9 +1,9 @@ /* zip_file_set_mtime.c -- set modification time of entry. - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -51,9 +51,18 @@ zip_file_set_mtime(zip_t *za, zip_uint64_t idx, time_t mtime, zip_flags_t flags) zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } e = za->entry + idx; + if (e->orig != NULL && e->orig->encryption_method == ZIP_EM_TRAD_PKWARE && !ZIP_ENTRY_CHANGED(e, ZIP_DIRENT_ENCRYPTION_METHOD) && !ZIP_ENTRY_DATA_CHANGED(e)) { + zip_error_set(&za->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } + if (e->changes == NULL) { if ((e->changes = _zip_dirent_clone(e->orig)) == NULL) { zip_error_set(&za->error, ZIP_ER_MEMORY, 0); diff --git a/core/deps/libzip/lib/zip_file_strerror.c b/core/deps/libzip/lib/zip_file_strerror.c index 2dcdba72b..5b5a00924 100644 --- a/core/deps/libzip/lib/zip_file_strerror.c +++ b/core/deps/libzip/lib/zip_file_strerror.c @@ -1,9 +1,9 @@ /* zip_file_sterror.c -- get string representation of zip file error - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_fopen.c b/core/deps/libzip/lib/zip_fopen.c index 2da0cc5f0..e3cde9be0 100644 --- a/core/deps/libzip/lib/zip_fopen.c +++ b/core/deps/libzip/lib/zip_fopen.c @@ -1,9 +1,9 @@ /* zip_fopen.c -- open file in zip archive for reading - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_fopen_encrypted.c b/core/deps/libzip/lib/zip_fopen_encrypted.c index 80160152a..d5880dcb9 100644 --- a/core/deps/libzip/lib/zip_fopen_encrypted.c +++ b/core/deps/libzip/lib/zip_fopen_encrypted.c @@ -1,9 +1,9 @@ /* zip_fopen_encrypted.c -- open file for reading with password - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_fopen_index.c b/core/deps/libzip/lib/zip_fopen_index.c index 5fd5f1a61..a449b83a0 100644 --- a/core/deps/libzip/lib/zip_fopen_index.c +++ b/core/deps/libzip/lib/zip_fopen_index.c @@ -1,9 +1,9 @@ /* zip_fopen_index.c -- open file in zip archive for reading by index - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_fopen_index_encrypted.c b/core/deps/libzip/lib/zip_fopen_index_encrypted.c index 280a5bd0c..40483709e 100644 --- a/core/deps/libzip/lib/zip_fopen_index_encrypted.c +++ b/core/deps/libzip/lib/zip_fopen_index_encrypted.c @@ -1,9 +1,9 @@ /* zip_fopen_index_encrypted.c -- open file for reading by index w/ password - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -49,11 +49,11 @@ zip_fopen_index_encrypted(zip_t *za, zip_uint64_t index, zip_flags_t flags, cons password = NULL; } - if ((src = _zip_source_zip_new(za, za, index, flags, 0, 0, password)) == NULL) + if ((src = zip_source_zip_file_create(za, index, flags, 0, -1, password, &za->error)) == NULL) return NULL; if (zip_source_open(src) < 0) { - _zip_error_set_from_source(&za->error, src); + zip_error_set_from_source(&za->error, src); zip_source_free(src); return NULL; } @@ -78,9 +78,7 @@ _zip_file_new(zip_t *za) { return NULL; } - zf->za = za; zip_error_init(&zf->error); - zf->eof = 0; zf->src = NULL; return zf; diff --git a/core/deps/libzip/lib/zip_fread.c b/core/deps/libzip/lib/zip_fread.c index ecf0c3b00..5b7da46ae 100644 --- a/core/deps/libzip/lib/zip_fread.c +++ b/core/deps/libzip/lib/zip_fread.c @@ -1,9 +1,9 @@ /* zip_fread.c -- read from file - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -50,11 +50,12 @@ zip_fread(zip_file_t *zf, void *outbuf, zip_uint64_t toread) { return -1; } - if ((zf->eof) || (toread == 0)) + if (toread == 0) { return 0; + } if ((n = zip_source_read(zf->src, outbuf, toread)) < 0) { - _zip_error_set_from_source(&zf->error, zf->src); + zip_error_set_from_source(&zf->error, zf->src); return -1; } diff --git a/core/deps/libzip/lib/zip_fseek.c b/core/deps/libzip/lib/zip_fseek.c index 21f344a6e..e68ffd367 100644 --- a/core/deps/libzip/lib/zip_fseek.c +++ b/core/deps/libzip/lib/zip_fseek.c @@ -1,9 +1,9 @@ /* zip_fseek.c -- seek in file - Copyright (C) 2016-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2016-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -43,9 +43,19 @@ zip_fseek(zip_file_t *zf, zip_int64_t offset, int whence) { return -1; if (zip_source_seek(zf->src, offset, whence) < 0) { - _zip_error_set_from_source(&zf->error, zf->src); + zip_error_set_from_source(&zf->error, zf->src); return -1; } return 0; } + + +ZIP_EXTERN int +zip_file_is_seekable(zip_file_t *zfile) { + if (!zfile) { + return -1; + } + + return zip_source_is_seekable(zfile->src); +} diff --git a/core/deps/libzip/lib/zip_ftell.c b/core/deps/libzip/lib/zip_ftell.c index 2b36770af..bf3b03d34 100644 --- a/core/deps/libzip/lib/zip_ftell.c +++ b/core/deps/libzip/lib/zip_ftell.c @@ -1,9 +1,9 @@ /* zip_ftell.c -- tell position in file - Copyright (C) 2016-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2016-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -46,7 +46,7 @@ zip_ftell(zip_file_t *zf) { res = zip_source_tell(zf->src); if (res < 0) { - _zip_error_set_from_source(&zf->error, zf->src); + zip_error_set_from_source(&zf->error, zf->src); return -1; } diff --git a/core/deps/libzip/lib/zip_get_archive_comment.c b/core/deps/libzip/lib/zip_get_archive_comment.c index 18a4b6106..ea9a00ab5 100644 --- a/core/deps/libzip/lib/zip_get_archive_comment.c +++ b/core/deps/libzip/lib/zip_get_archive_comment.c @@ -1,9 +1,9 @@ /* zip_get_archive_comment.c -- get archive comment - Copyright (C) 2006-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2006-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_get_archive_flag.c b/core/deps/libzip/lib/zip_get_archive_flag.c index 3185ac53a..fc200bdc4 100644 --- a/core/deps/libzip/lib/zip_get_archive_flag.c +++ b/core/deps/libzip/lib/zip_get_archive_flag.c @@ -1,9 +1,9 @@ /* zip_get_archive_flag.c -- get archive global flag - Copyright (C) 2008-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2008-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_get_encryption_implementation.c b/core/deps/libzip/lib/zip_get_encryption_implementation.c index c1643dafe..72e48fe87 100644 --- a/core/deps/libzip/lib/zip_get_encryption_implementation.c +++ b/core/deps/libzip/lib/zip_get_encryption_implementation.c @@ -1,9 +1,9 @@ /* zip_get_encryption_implementation.c -- get encryption implementation - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_get_file_comment.c b/core/deps/libzip/lib/zip_get_file_comment.c index 28429bc87..d58e22ba6 100644 --- a/core/deps/libzip/lib/zip_get_file_comment.c +++ b/core/deps/libzip/lib/zip_get_file_comment.c @@ -1,9 +1,9 @@ /* zip_get_file_comment.c -- get file comment - Copyright (C) 2006-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2006-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_get_name.c b/core/deps/libzip/lib/zip_get_name.c index 1a4bab8f3..4828d7810 100644 --- a/core/deps/libzip/lib/zip_get_name.c +++ b/core/deps/libzip/lib/zip_get_name.c @@ -1,9 +1,9 @@ /* zip_get_name.c -- get filename for a file in zip file - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_get_num_entries.c b/core/deps/libzip/lib/zip_get_num_entries.c index 3b18c7479..667dc5117 100644 --- a/core/deps/libzip/lib/zip_get_num_entries.c +++ b/core/deps/libzip/lib/zip_get_num_entries.c @@ -1,9 +1,9 @@ /* zip_get_num_entries.c -- get number of entries in archive - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_get_num_files.c b/core/deps/libzip/lib/zip_get_num_files.c index 23d7c9c96..140e34f90 100644 --- a/core/deps/libzip/lib/zip_get_num_files.c +++ b/core/deps/libzip/lib/zip_get_num_files.c @@ -1,9 +1,9 @@ /* zip_get_num_files.c -- get number of files in archive - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_hash.c b/core/deps/libzip/lib/zip_hash.c index 56e8d78ca..d3a954ec5 100644 --- a/core/deps/libzip/lib/zip_hash.c +++ b/core/deps/libzip/lib/zip_hash.c @@ -1,9 +1,9 @@ /* zip_hash.c -- hash table string -> uint64 - Copyright (C) 2015-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2015-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_io_util.c b/core/deps/libzip/lib/zip_io_util.c index 3678dd59d..9fcd10b4b 100644 --- a/core/deps/libzip/lib/zip_io_util.c +++ b/core/deps/libzip/lib/zip_io_util.c @@ -1,9 +1,9 @@ /* zip_io_util.c -- I/O helper functions - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -31,8 +31,10 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include #include #include +#include #include "zipint.h" @@ -46,7 +48,7 @@ _zip_read(zip_source_t *src, zip_uint8_t *b, zip_uint64_t length, zip_error_t *e } if ((n = zip_source_read(src, b, length)) < 0) { - _zip_error_set_from_source(error, src); + zip_error_set_from_source(error, src); return -1; } @@ -81,7 +83,7 @@ _zip_read_data(zip_buffer_t *buffer, zip_source_t *src, size_t length, bool nulp free(r); return NULL; } - memcpy(r, data, length); + (void)memcpy_s(r, length, data, length); } else { if (_zip_read(src, r, length, error) < 0) { @@ -122,7 +124,7 @@ _zip_write(zip_t *za, const void *data, zip_uint64_t length) { zip_int64_t n; if ((n = zip_source_write(za->src, data, length)) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return -1; } if ((zip_uint64_t)n != length) { @@ -130,5 +132,15 @@ _zip_write(zip_t *za, const void *data, zip_uint64_t length) { return -1; } + if (za->write_crc != NULL) { + zip_uint64_t position = 0; + while (position < length) { + zip_uint64_t nn = ZIP_MIN(UINT_MAX, length - position); + + *za->write_crc = (zip_uint32_t)crc32(*za->write_crc, (const Bytef *)data + position, (uInt)nn); + position += nn; + } + } + return 0; } diff --git a/core/deps/libzip/lib/zip_libzip_version.c b/core/deps/libzip/lib/zip_libzip_version.c index df1e55b9c..4200727fc 100644 --- a/core/deps/libzip/lib/zip_libzip_version.c +++ b/core/deps/libzip/lib/zip_libzip_version.c @@ -1,9 +1,9 @@ /* zip_libzip_version.c -- return run-time version of library - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_memdup.c b/core/deps/libzip/lib/zip_memdup.c index 169186087..75d72c616 100644 --- a/core/deps/libzip/lib/zip_memdup.c +++ b/core/deps/libzip/lib/zip_memdup.c @@ -1,9 +1,9 @@ /* zip_memdup.c -- internal zip function, "strdup" with len - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -50,7 +50,7 @@ _zip_memdup(const void *mem, size_t len, zip_error_t *error) { return NULL; } - memcpy(ret, mem, len); + (void)memcpy_s(ret, len, mem, len); return ret; } diff --git a/core/deps/libzip/lib/zip_name_locate.c b/core/deps/libzip/lib/zip_name_locate.c index 308d843c2..4248dc2d3 100644 --- a/core/deps/libzip/lib/zip_name_locate.c +++ b/core/deps/libzip/lib/zip_name_locate.c @@ -1,9 +1,9 @@ /* zip_name_locate.c -- get index by name - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -49,18 +49,38 @@ zip_name_locate(zip_t *za, const char *fname, zip_flags_t flags) { zip_int64_t _zip_name_locate(zip_t *za, const char *fname, zip_flags_t flags, zip_error_t *error) { int (*cmp)(const char *, const char *); + size_t fname_length; + zip_string_t *str = NULL; const char *fn, *p; zip_uint64_t i; - if (za == NULL) + if (za == NULL) { return -1; + } if (fname == NULL) { zip_error_set(error, ZIP_ER_INVAL, 0); return -1; } - if (flags & (ZIP_FL_NOCASE | ZIP_FL_NODIR | ZIP_FL_ENC_CP437)) { + fname_length = strlen(fname); + + if (fname_length > ZIP_UINT16_MAX) { + zip_error_set(error, ZIP_ER_INVAL, 0); + return -1; + } + + if ((flags & (ZIP_FL_ENC_UTF_8 | ZIP_FL_ENC_RAW)) == 0 && fname[0] != '\0') { + if ((str = _zip_string_new((const zip_uint8_t *)fname, (zip_uint16_t)strlen(fname), flags, error)) == NULL) { + return -1; + } + if ((fname = (const char *)_zip_string_get(str, NULL, 0, error)) == NULL) { + _zip_string_free(str); + return -1; + } + } + + if (flags & (ZIP_FL_NOCASE | ZIP_FL_NODIR | ZIP_FL_ENC_RAW | ZIP_FL_ENC_STRICT)) { /* can't use hash table */ cmp = (flags & ZIP_FL_NOCASE) ? strcasecmp : strcmp; @@ -79,14 +99,18 @@ _zip_name_locate(zip_t *za, const char *fname, zip_flags_t flags, zip_error_t *e if (cmp(fname, fn) == 0) { _zip_error_clear(error); + _zip_string_free(str); return (zip_int64_t)i; } } zip_error_set(error, ZIP_ER_NOENT, 0); + _zip_string_free(str); return -1; } else { - return _zip_hash_lookup(za->names, (const zip_uint8_t *)fname, flags, error); + zip_int64_t ret = _zip_hash_lookup(za->names, (const zip_uint8_t *)fname, flags, error); + _zip_string_free(str); + return ret; } } diff --git a/core/deps/libzip/lib/zip_new.c b/core/deps/libzip/lib/zip_new.c index d2a5fa14d..4f69c8a2f 100644 --- a/core/deps/libzip/lib/zip_new.c +++ b/core/deps/libzip/lib/zip_new.c @@ -1,9 +1,9 @@ /* zip_new.c -- create and init struct zip - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_open.c b/core/deps/libzip/lib/zip_open.c index 292bfc84d..ee7e9decb 100644 --- a/core/deps/libzip/lib/zip_open.c +++ b/core/deps/libzip/lib/zip_open.c @@ -1,9 +1,9 @@ /* zip_open.c -- open zip archive by name - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -32,6 +32,7 @@ */ +#include #include #include #include @@ -41,10 +42,11 @@ typedef enum { EXISTS_ERROR = -1, EXISTS_NOT = 0, EXISTS_OK } exists_t; static zip_t *_zip_allocate_new(zip_source_t *src, unsigned int flags, zip_error_t *error); static zip_int64_t _zip_checkcons(zip_t *za, zip_cdir_t *cdir, zip_error_t *error); +static void zip_check_torrentzip(zip_t *za, const zip_cdir_t *cdir); static zip_cdir_t *_zip_find_central_dir(zip_t *za, zip_uint64_t len); static exists_t _zip_file_exists(zip_source_t *src, zip_error_t *error); static int _zip_headercomp(const zip_dirent_t *, const zip_dirent_t *); -static unsigned char *_zip_memmem(const unsigned char *, size_t, const unsigned char *, size_t); +static const unsigned char *_zip_memmem(const unsigned char *, size_t, const unsigned char *, size_t); static zip_cdir_t *_zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_error_t *error); static zip_cdir_t *_zip_read_eocd(zip_buffer_t *buffer, zip_uint64_t buf_offset, unsigned int flags, zip_error_t *error); static zip_cdir_t *_zip_read_eocd64(zip_source_t *src, zip_buffer_t *buffer, zip_uint64_t buf_offset, unsigned int flags, zip_error_t *error); @@ -77,9 +79,6 @@ zip_open(const char *fn, int _flags, int *zep) { ZIP_EXTERN zip_t * zip_open_from_source(zip_source_t *src, int _flags, zip_error_t *error) { - static zip_int64_t needed_support_read = -1; - static zip_int64_t needed_support_write = -1; - unsigned int flags; zip_int64_t supported; exists_t exists; @@ -91,15 +90,11 @@ zip_open_from_source(zip_source_t *src, int _flags, zip_error_t *error) { flags = (unsigned int)_flags; supported = zip_source_supports(src); - if (needed_support_read == -1) { - needed_support_read = zip_source_make_command_bitmap(ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_CLOSE, ZIP_SOURCE_SEEK, ZIP_SOURCE_TELL, ZIP_SOURCE_STAT, -1); - needed_support_write = zip_source_make_command_bitmap(ZIP_SOURCE_BEGIN_WRITE, ZIP_SOURCE_COMMIT_WRITE, ZIP_SOURCE_ROLLBACK_WRITE, ZIP_SOURCE_SEEK_WRITE, ZIP_SOURCE_TELL_WRITE, ZIP_SOURCE_REMOVE, -1); - } - if ((supported & needed_support_read) != needed_support_read) { + if ((supported & ZIP_SOURCE_SUPPORTS_SEEKABLE) != ZIP_SOURCE_SUPPORTS_SEEKABLE) { zip_error_set(error, ZIP_ER_OPNOTSUPP, 0); return NULL; } - if ((supported & needed_support_write) != needed_support_write) { + if ((supported & ZIP_SOURCE_SUPPORTS_WRITABLE) != ZIP_SOURCE_SUPPORTS_WRITABLE) { flags |= ZIP_RDONLY; } @@ -127,7 +122,7 @@ zip_open_from_source(zip_source_t *src, int _flags, zip_error_t *error) { return NULL; } if (zip_source_open(src) < 0) { - _zip_error_set_from_source(error, src); + zip_error_set_from_source(error, src); return NULL; } @@ -158,7 +153,7 @@ _zip_open(zip_source_t *src, unsigned int flags, zip_error_t *error) { zip_stat_init(&st); if (zip_source_stat(src, &st) < 0) { - _zip_error_set_from_source(error, src); + zip_error_set_from_source(error, src); return NULL; } if ((st.valid & ZIP_STAT_SIZE) == 0) { @@ -188,7 +183,16 @@ _zip_open(zip_source_t *src, unsigned int flags, zip_error_t *error) { za->entry = cdir->entry; za->nentry = cdir->nentry; za->nentry_alloc = cdir->nentry_alloc; - za->comment_orig = cdir->comment; + + zip_check_torrentzip(za, cdir); + + if (ZIP_IS_TORRENTZIP(za)) { + /* Torrentzip uses the archive comment to detect changes by tools that are not torrentzip aware. */ + _zip_string_free(cdir->comment); + } + else { + za->comment_orig = cdir->comment; + } free(cdir); @@ -224,8 +228,14 @@ void _zip_set_open_error(int *zep, const zip_error_t *err, int ze) { if (err) { ze = zip_error_code_zip(err); - if (zip_error_system_type(err) == ZIP_ET_SYS) { - errno = zip_error_code_system(err); + switch (zip_error_system_type(err)) { + case ZIP_ET_SYS: + case ZIP_ET_LIBZIP: + errno = zip_error_code_system(err); + break; + + default: + break; } } @@ -277,7 +287,7 @@ _zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_err if (cd->offset + cd->size > buf_offset + eocd_offset) { /* cdir spans past EOCD record */ - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_OVERLAPS_EOCD); _zip_cdir_free(cd); return NULL; } @@ -289,7 +299,7 @@ _zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_err tail_len = _zip_buffer_left(buffer); if (tail_len < comment_len || ((za->open_flags & ZIP_CHECKCONS) && tail_len != comment_len)) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_COMMENT_LENGTH_INVALID); _zip_cdir_free(cd); return NULL; } @@ -308,7 +318,7 @@ _zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_err _zip_buffer_set_offset(buffer, cd->offset - buf_offset); if ((data = _zip_buffer_get(buffer, cd->size)) == NULL) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_LENGTH_INVALID); _zip_cdir_free(cd); return NULL; } @@ -322,7 +332,7 @@ _zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_err cd_buffer = NULL; if (zip_source_seek(za->src, (zip_int64_t)cd->offset, SEEK_SET) < 0) { - _zip_error_set_from_source(error, za->src); + zip_error_set_from_source(error, za->src); _zip_cdir_free(cd); return NULL; } @@ -358,8 +368,11 @@ _zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_err } if ((cd->entry[i].orig = _zip_dirent_new()) == NULL || (entry_size = _zip_dirent_read(cd->entry[i].orig, za->src, cd_buffer, false, error)) < 0) { - if (grown && zip_error_code_zip(error) == ZIP_ER_NOZIP) { - zip_error_set(error, ZIP_ER_INCONS, 0); + if (zip_error_code_zip(error) == ZIP_ER_INCONS) { + zip_error_set(error, ZIP_ER_INCONS, ADD_INDEX_TO_DETAIL(zip_error_code_system(error), i)); + } + else if (grown && zip_error_code_zip(error) == ZIP_ER_NOZIP) { + zip_error_set(error, ZIP_ER_INCONS, MAKE_DETAIL_WITH_INDEX(ZIP_ER_DETAIL_CDIR_ENTRY_INVALID, i)); } _zip_cdir_free(cd); _zip_buffer_free(cd_buffer); @@ -370,7 +383,7 @@ _zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_err } if (i != cd->nentry || left > 0) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_WRONG_ENTRIES_COUNT); _zip_buffer_free(cd_buffer); _zip_cdir_free(cd); return NULL; @@ -386,7 +399,7 @@ _zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_err zip_int64_t offset = zip_source_tell(za->src); if (offset < 0) { - _zip_error_set_from_source(error, za->src); + zip_error_set_from_source(error, za->src); _zip_cdir_free(cd); return NULL; } @@ -394,7 +407,7 @@ _zip_read_cdir(zip_t *za, zip_buffer_t *buffer, zip_uint64_t buf_offset, zip_err } if (!ok) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_LENGTH_INVALID); _zip_buffer_free(cd_buffer); _zip_cdir_free(cd); return NULL; @@ -443,17 +456,20 @@ _zip_checkcons(zip_t *za, zip_cdir_t *cd, zip_error_t *error) { } if (zip_source_seek(za->src, (zip_int64_t)cd->entry[i].orig->offset, SEEK_SET) < 0) { - _zip_error_set_from_source(error, za->src); + zip_error_set_from_source(error, za->src); return -1; } if (_zip_dirent_read(&temp, za->src, NULL, true, error) == -1) { + if (zip_error_code_zip(error) == ZIP_ER_INCONS) { + zip_error_set(error, ZIP_ER_INCONS, ADD_INDEX_TO_DETAIL(zip_error_code_system(error), i)); + } _zip_dirent_finalize(&temp); return -1; } if (_zip_headercomp(cd->entry[i].orig, &temp) != 0) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, MAKE_DETAIL_WITH_INDEX(ZIP_ER_DETAIL_ENTRY_HEADER_MISMATCH, i)); _zip_dirent_finalize(&temp); return -1; } @@ -486,9 +502,20 @@ _zip_headercomp(const zip_dirent_t *central, const zip_dirent_t *local) { if ((central->crc != local->crc) || (central->comp_size != local->comp_size) || (central->uncomp_size != local->uncomp_size)) { /* InfoZip stores valid values in local header even when data descriptor is used. - This is in violation of the appnote. */ - if (((local->bitflags & ZIP_GPBF_DATA_DESCRIPTOR) == 0 || local->crc != 0 || local->comp_size != 0 || local->uncomp_size != 0)) + This is in violation of the appnote. + macOS Archive sets the compressed size even when data descriptor is used ( but not the others), + also in violation of the appnote. + */ + /* if data descriptor is not used, the values must match */ + if ((local->bitflags & ZIP_GPBF_DATA_DESCRIPTOR) == 0) { return -1; + } + /* when using a data descriptor, the local header value must be zero or match */ + if ((local->crc != 0 && central->crc != local->crc) || + (local->comp_size != 0 && central->comp_size != local->comp_size) || + (local->uncomp_size != 0 && central->uncomp_size != local->uncomp_size)) { + return -1; + } } return 0; @@ -505,10 +532,15 @@ _zip_allocate_new(zip_source_t *src, unsigned int flags, zip_error_t *error) { za->src = src; za->open_flags = flags; + za->flags = 0; + za->ch_flags = 0; + za->write_crc = NULL; + if (flags & ZIP_RDONLY) { za->flags |= ZIP_AFL_RDONLY; za->ch_flags |= ZIP_AFL_RDONLY; } + return za; } @@ -537,7 +569,7 @@ _zip_file_exists(zip_source_t *src, zip_error_t *error) { static zip_cdir_t * _zip_find_central_dir(zip_t *za, zip_uint64_t len) { zip_cdir_t *cdir, *cdirnew; - zip_uint8_t *match; + const zip_uint8_t *match; zip_int64_t buf_offset; zip_uint64_t buflen; zip_int64_t a; @@ -560,7 +592,7 @@ _zip_find_central_dir(zip_t *za, zip_uint64_t len) { } } if ((buf_offset = zip_source_tell(za->src)) < 0) { - _zip_error_set_from_source(&za->error, za->src); + zip_error_set_from_source(&za->error, za->src); return NULL; } @@ -577,7 +609,8 @@ _zip_find_central_dir(zip_t *za, zip_uint64_t len) { zip_error_set(&error, ZIP_ER_NOZIP, 0); match = _zip_buffer_get(buffer, 0); - while ((match = _zip_memmem(match, _zip_buffer_left(buffer) - (EOCDLEN - 4), (const unsigned char *)EOCD_MAGIC, 4)) != NULL) { + /* The size of buffer never greater than CDBUFSIZE. */ + while (_zip_buffer_left(buffer) >= EOCDLEN && (match = _zip_memmem(match, (size_t)_zip_buffer_left(buffer) - (EOCDLEN - 4), (const unsigned char *)EOCD_MAGIC, 4)) != NULL) { _zip_buffer_set_offset(buffer, (zip_uint64_t)(match - _zip_buffer_data(buffer))); if ((cdirnew = _zip_read_cdir(za, buffer, (zip_uint64_t)buf_offset, &error)) != NULL) { if (cdir) { @@ -622,19 +655,28 @@ _zip_find_central_dir(zip_t *za, zip_uint64_t len) { } -static unsigned char * -_zip_memmem(const unsigned char *big, size_t biglen, const unsigned char *little, size_t littlelen) { +static const unsigned char *_zip_memmem(const unsigned char *big, size_t biglen, const unsigned char *little, size_t littlelen) { const unsigned char *p; - if ((biglen < littlelen) || (littlelen == 0)) - return NULL; - p = big - 1; - while ((p = (const unsigned char *)memchr(p + 1, little[0], (size_t)(big - (p + 1)) + (size_t)(biglen - littlelen) + 1)) != NULL) { - if (memcmp(p + 1, little + 1, littlelen - 1) == 0) - return (unsigned char *)p; + if (littlelen == 0) { + return big; } - return NULL; + if (biglen < littlelen) { + return NULL; + } + + p = big; + while (true) { + p = (const unsigned char *)memchr(p, little[0], biglen - (littlelen - 1) - (size_t)(p - big)); + if (p == NULL) { + return NULL; + } + if (memcmp(p + 1, little + 1, littlelen - 1) == 0) { + return p; + } + p += 1; + } } @@ -644,7 +686,7 @@ _zip_read_eocd(zip_buffer_t *buffer, zip_uint64_t buf_offset, unsigned int flags zip_uint64_t i, nentry, size, offset, eocd_offset; if (_zip_buffer_left(buffer) < EOCDLEN) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_EOCD_LENGTH_INVALID); return NULL; } @@ -677,12 +719,12 @@ _zip_read_eocd(zip_buffer_t *buffer, zip_uint64_t buf_offset, unsigned int flags if (offset + size > buf_offset + eocd_offset) { /* cdir spans past EOCD record */ - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_OVERLAPS_EOCD); return NULL; } if ((flags & ZIP_CHECKCONS) && offset + size != buf_offset + eocd_offset) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_LENGTH_INVALID); return NULL; } @@ -723,7 +765,7 @@ _zip_read_eocd64(zip_source_t *src, zip_buffer_t *buffer, zip_uint64_t buf_offse /* does EOCD fit before EOCD locator? */ if (eocd_offset + EOCD64LEN > eocdloc_offset + buf_offset) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_EOCD64_OVERLAPS_EOCD); return NULL; } @@ -734,7 +776,7 @@ _zip_read_eocd64(zip_source_t *src, zip_buffer_t *buffer, zip_uint64_t buf_offse } else { if (zip_source_seek(src, (zip_int64_t)eocd_offset, SEEK_SET) < 0) { - _zip_error_set_from_source(error, src); + zip_error_set_from_source(error, src); return NULL; } if ((buffer = _zip_buffer_new_from_source(src, EOCD64LEN, eocd, error)) == NULL) { @@ -744,7 +786,7 @@ _zip_read_eocd64(zip_source_t *src, zip_buffer_t *buffer, zip_uint64_t buf_offse } if (memcmp(_zip_buffer_get(buffer, 4), EOCD64_MAGIC, 4) != 0) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_EOCD64_WRONG_MAGIC); if (free_buffer) { _zip_buffer_free(buffer); } @@ -756,7 +798,7 @@ _zip_read_eocd64(zip_source_t *src, zip_buffer_t *buffer, zip_uint64_t buf_offse /* is there a hole between EOCD and EOCD locator, or do they overlap? */ if ((flags & ZIP_CHECKCONS) && size + eocd_offset + 12 != buf_offset + eocdloc_offset) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_EOCD64_OVERLAPS_EOCD); if (free_buffer) { _zip_buffer_free(buffer); } @@ -778,7 +820,7 @@ _zip_read_eocd64(zip_source_t *src, zip_buffer_t *buffer, zip_uint64_t buf_offse eocd_disk = eocd_disk64; } if ((flags & ZIP_CHECKCONS) && (eocd_disk != eocd_disk64 || num_disks != num_disks64)) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_EOCD64_MISMATCH); if (free_buffer) { _zip_buffer_free(buffer); } @@ -825,16 +867,16 @@ _zip_read_eocd64(zip_source_t *src, zip_buffer_t *buffer, zip_uint64_t buf_offse } if (offset + size > buf_offset + eocd_offset) { /* cdir spans past EOCD record */ - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_OVERLAPS_EOCD); return NULL; } if ((flags & ZIP_CHECKCONS) && offset + size != buf_offset + eocd_offset) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_OVERLAPS_EOCD); return NULL; } if (nentry > size / CDENTRYSIZE) { - zip_error_set(error, ZIP_ER_INCONS, 0); + zip_error_set(error, ZIP_ER_INCONS, ZIP_ER_DETAIL_CDIR_INVALID); return NULL; } @@ -847,3 +889,79 @@ _zip_read_eocd64(zip_source_t *src, zip_buffer_t *buffer, zip_uint64_t buf_offse return cd; } + + +static int decode_hex(char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + else if (c >= 'A' && c <= 'F') { + return c - 'A' + 10; + } + else { + return -1; + } +} + +/* _zip_check_torrentzip: + check whether ZA has a valid TORRENTZIP comment, i.e. is torrentzipped */ + +static void zip_check_torrentzip(zip_t *za, const zip_cdir_t *cdir) { + zip_uint32_t crc_should; + char buf[8+1]; + size_t i; + + if (cdir == NULL) { + return; + } + + if (_zip_string_length(cdir->comment) != TORRENTZIP_SIGNATURE_LENGTH + TORRENTZIP_CRC_LENGTH + || strncmp((const char *)cdir->comment->raw, TORRENTZIP_SIGNATURE, TORRENTZIP_SIGNATURE_LENGTH) != 0) + return; + + memcpy(buf, cdir->comment->raw + TORRENTZIP_SIGNATURE_LENGTH, TORRENTZIP_CRC_LENGTH); + buf[TORRENTZIP_CRC_LENGTH] = '\0'; + crc_should = 0; + for (i = 0; i < TORRENTZIP_CRC_LENGTH; i += 2) { + int low, high; + high = decode_hex((buf[i])); + low = decode_hex(buf[i + 1]); + if (high < 0 || low < 0) { + return; + } + crc_should = (crc_should << 8) + (high << 4) + low; + } + + { + zip_stat_t st; + zip_source_t* src_window; + zip_source_t* src_crc; + zip_uint8_t buffer[512]; + zip_int64_t ret; + + zip_stat_init(&st); + st.valid |= ZIP_STAT_SIZE | ZIP_STAT_CRC; + st.size = cdir->size; + st.crc = crc_should; + if ((src_window = _zip_source_window_new(za->src, cdir->offset, cdir->size, &st, 0, NULL, NULL, 0, false, NULL)) == NULL) { + return; + } + if ((src_crc = zip_source_crc_create(src_window, 1, NULL)) == NULL) { + zip_source_free(src_window); + return; + } + if (zip_source_open(src_crc) != 0) { + zip_source_free(src_crc); + return; + } + while ((ret = zip_source_read(src_crc, buffer, sizeof(buffer))) > 0) { + } + zip_source_free(src_crc); + if (ret < 0) { + return; + } + } + + /* TODO: if check consistency, check cdir entries for valid values */ + za->flags |= ZIP_AFL_IS_TORRENTZIP; +} diff --git a/core/deps/libzip/lib/zip_pkware.c b/core/deps/libzip/lib/zip_pkware.c index 1b1b461a0..6a8c9fcde 100644 --- a/core/deps/libzip/lib/zip_pkware.c +++ b/core/deps/libzip/lib/zip_pkware.c @@ -3,7 +3,7 @@ Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_progress.c b/core/deps/libzip/lib/zip_progress.c index a881df476..e080514b1 100644 --- a/core/deps/libzip/lib/zip_progress.c +++ b/core/deps/libzip/lib/zip_progress.c @@ -3,7 +3,7 @@ Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_random_unix.c b/core/deps/libzip/lib/zip_random_unix.c index 4aa9cc26e..867df7900 100644 --- a/core/deps/libzip/lib/zip_random_unix.c +++ b/core/deps/libzip/lib/zip_random_unix.c @@ -1,9 +1,9 @@ /* zip_random_unix.c -- fill the user's buffer with random stuff (Unix version) - Copyright (C) 2016-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2016-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -83,6 +83,11 @@ zip_secure_random(zip_uint8_t *buffer, zip_uint16_t length) { #ifndef HAVE_RANDOM_UINT32 #include +#ifndef HAVE_RANDOM +#define srandom srand +#define random rand +#endif + zip_uint32_t zip_random_uint32(void) { static bool seeded = false; @@ -95,6 +100,7 @@ zip_random_uint32(void) { if (!seeded) { srandom((unsigned int)time(NULL)); + seeded = true; } return (zip_uint32_t)random(); diff --git a/core/deps/libzip/lib/zip_random_uwp.c b/core/deps/libzip/lib/zip_random_uwp.c index 7cff98e98..0883ce454 100644 --- a/core/deps/libzip/lib/zip_random_uwp.c +++ b/core/deps/libzip/lib/zip_random_uwp.c @@ -1,9 +1,9 @@ /* zip_random_uwp.c -- fill the user's buffer with random stuff (UWP version) - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -41,18 +41,17 @@ #include #include -#include ZIP_EXTERN bool zip_secure_random(zip_uint8_t *buffer, zip_uint16_t length) { BCRYPT_ALG_HANDLE hAlg = NULL; NTSTATUS hr = BCryptOpenAlgorithmProvider(&hAlg, BCRYPT_RNG_ALGORITHM, MS_PRIMITIVE_PROVIDER, 0); - if (hr != STATUS_SUCCESS || hAlg == NULL) { + if (!BCRYPT_SUCCESS(hr) || hAlg == NULL) { return false; } hr = BCryptGenRandom(&hAlg, buffer, length, 0); BCryptCloseAlgorithmProvider(&hAlg, 0); - if (hr != STATUS_SUCCESS) { + if (!BCRYPT_SUCCESS(hr)) { return false; } return true; @@ -75,6 +74,7 @@ zip_random_uint32(void) { if (!seeded) { srand((unsigned int)time(NULL)); + seeded = true; } return (zip_uint32_t)rand(); diff --git a/core/deps/libzip/lib/zip_random_win32.c b/core/deps/libzip/lib/zip_random_win32.c index 251735778..789b9c205 100644 --- a/core/deps/libzip/lib/zip_random_win32.c +++ b/core/deps/libzip/lib/zip_random_win32.c @@ -1,9 +1,9 @@ /* zip_random_win32.c -- fill the user's buffer with random stuff (Windows version) - Copyright (C) 2016-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2016-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -74,6 +74,7 @@ zip_random_uint32(void) { if (!seeded) { srand((unsigned int)time(NULL)); + seeded = true; } return (zip_uint32_t)rand(); diff --git a/core/deps/libzip/lib/zip_rename.c b/core/deps/libzip/lib/zip_rename.c index eb1b3956b..c89b06c9e 100644 --- a/core/deps/libzip/lib/zip_rename.c +++ b/core/deps/libzip/lib/zip_rename.c @@ -1,9 +1,9 @@ /* zip_rename.c -- rename file in zip archive - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_replace.c b/core/deps/libzip/lib/zip_replace.c index 2140223ac..96c083c3d 100644 --- a/core/deps/libzip/lib/zip_replace.c +++ b/core/deps/libzip/lib/zip_replace.c @@ -1,9 +1,9 @@ /* zip_replace.c -- replace file via callback function - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_set_archive_comment.c b/core/deps/libzip/lib/zip_set_archive_comment.c index ce0c5d38a..7d06688e3 100644 --- a/core/deps/libzip/lib/zip_set_archive_comment.c +++ b/core/deps/libzip/lib/zip_set_archive_comment.c @@ -1,9 +1,9 @@ /* zip_set_archive_comment.c -- set archive comment - Copyright (C) 2006-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2006-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -45,6 +45,10 @@ zip_set_archive_comment(zip_t *za, const char *comment, zip_uint16_t len) { zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } if (len > 0 && comment == NULL) { zip_error_set(&za->error, ZIP_ER_INVAL, 0); diff --git a/core/deps/libzip/lib/zip_set_archive_flag.c b/core/deps/libzip/lib/zip_set_archive_flag.c index fe9f62ff0..834ef5bac 100644 --- a/core/deps/libzip/lib/zip_set_archive_flag.c +++ b/core/deps/libzip/lib/zip_set_archive_flag.c @@ -1,9 +1,9 @@ /* zip_get_archive_flag.c -- set archive global flag - Copyright (C) 2008-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2008-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -39,15 +39,26 @@ ZIP_EXTERN int zip_set_archive_flag(zip_t *za, zip_flags_t flag, int value) { unsigned int new_flags; - if (value) + if (flag == ZIP_AFL_IS_TORRENTZIP) { + zip_error_set(&za->error, ZIP_ER_INVAL, 0); + return -1; + } + + /* TODO: when setting ZIP_AFL_WANT_TORRENTZIP, we should error out if any changes have been made that are not allowed for torrentzip. */ + + if (value) { new_flags = za->ch_flags | flag; - else + } + else { new_flags = za->ch_flags & ~flag; + } - if (new_flags == za->ch_flags) + if (new_flags == za->ch_flags) { return 0; + } - if (ZIP_IS_RDONLY(za)) { + /* Allow removing ZIP_AFL_RDONLY if manually set, not if archive was opened read-only. */ + if (za->flags & ZIP_AFL_RDONLY) { zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } diff --git a/core/deps/libzip/lib/zip_set_default_password.c b/core/deps/libzip/lib/zip_set_default_password.c index d89394927..4bab513f4 100644 --- a/core/deps/libzip/lib/zip_set_default_password.c +++ b/core/deps/libzip/lib/zip_set_default_password.c @@ -1,9 +1,9 @@ /* zip_set_default_password.c -- set default password for decryption - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_set_file_comment.c b/core/deps/libzip/lib/zip_set_file_comment.c index d19121fa1..5d2b0b8a0 100644 --- a/core/deps/libzip/lib/zip_set_file_comment.c +++ b/core/deps/libzip/lib/zip_set_file_comment.c @@ -1,9 +1,9 @@ /* zip_set_file_comment.c -- set comment for file in archive - Copyright (C) 2006-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2006-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_set_file_compression.c b/core/deps/libzip/lib/zip_set_file_compression.c index 511c7d515..a193bb77f 100644 --- a/core/deps/libzip/lib/zip_set_file_compression.c +++ b/core/deps/libzip/lib/zip_set_file_compression.c @@ -1,9 +1,9 @@ /* zip_set_file_compression.c -- set compression for file in archive - Copyright (C) 2012-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2012-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -40,7 +40,7 @@ zip_set_file_compression(zip_t *za, zip_uint64_t idx, zip_int32_t method, zip_ui zip_entry_t *e; zip_int32_t old_method; - if (idx >= za->nentry || flags > 9) { + if (idx >= za->nentry) { zip_error_set(&za->error, ZIP_ER_INVAL, 0); return -1; } @@ -49,6 +49,10 @@ zip_set_file_compression(zip_t *za, zip_uint64_t idx, zip_int32_t method, zip_ui zip_error_set(&za->error, ZIP_ER_RDONLY, 0); return -1; } + if (ZIP_WANT_TORRENTZIP(za)) { + zip_error_set(&za->error, ZIP_ER_NOT_ALLOWED, 0); + return -1; + } if (!zip_compression_method_supported(method, true)) { zip_error_set(&za->error, ZIP_ER_COMPNOTSUPP, 0); diff --git a/core/deps/libzip/lib/zip_set_name.c b/core/deps/libzip/lib/zip_set_name.c index 52676857e..f1bf703e4 100644 --- a/core/deps/libzip/lib/zip_set_name.c +++ b/core/deps/libzip/lib/zip_set_name.c @@ -1,9 +1,9 @@ /* zip_set_name.c -- rename helper function - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_accept_empty.c b/core/deps/libzip/lib/zip_source_accept_empty.c index 754b80b76..e772aeea9 100644 --- a/core/deps/libzip/lib/zip_source_accept_empty.c +++ b/core/deps/libzip/lib/zip_source_accept_empty.c @@ -1,9 +1,9 @@ /* zip_source_accept_empty.c -- if empty source is a valid archive - Copyright (C) 2019-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2019-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,7 +37,7 @@ bool zip_source_accept_empty(zip_source_t *src) { - int ret; + zip_int64_t ret; if ((zip_source_supports(src) & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_ACCEPT_EMPTY)) == 0) { if (ZIP_SOURCE_IS_LAYERED(src)) { @@ -46,7 +46,7 @@ zip_source_accept_empty(zip_source_t *src) { return true; } - ret = (int)_zip_source_call(src, NULL, 0, ZIP_SOURCE_ACCEPT_EMPTY); + ret = _zip_source_call(src, NULL, 0, ZIP_SOURCE_ACCEPT_EMPTY); return ret != 0; } diff --git a/core/deps/libzip/lib/zip_source_begin_write.c b/core/deps/libzip/lib/zip_source_begin_write.c index 3981dc1f9..4a9d5d5d6 100644 --- a/core/deps/libzip/lib/zip_source_begin_write.c +++ b/core/deps/libzip/lib/zip_source_begin_write.c @@ -1,9 +1,9 @@ /* zip_source_begin_write.c -- start a new file for writing - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,6 +37,11 @@ ZIP_EXTERN int zip_source_begin_write(zip_source_t *src) { + if (ZIP_SOURCE_IS_LAYERED(src)) { + zip_error_set(&src->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } + if (ZIP_SOURCE_IS_OPEN_WRITING(src)) { zip_error_set(&src->error, ZIP_ER_INVAL, 0); return -1; diff --git a/core/deps/libzip/lib/zip_source_begin_write_cloning.c b/core/deps/libzip/lib/zip_source_begin_write_cloning.c index bfc63c8a6..df195fcd4 100644 --- a/core/deps/libzip/lib/zip_source_begin_write_cloning.c +++ b/core/deps/libzip/lib/zip_source_begin_write_cloning.c @@ -1,9 +1,9 @@ /* zip_source_begin_write_cloning.c -- clone part of file for writing - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,6 +37,11 @@ ZIP_EXTERN int zip_source_begin_write_cloning(zip_source_t *src, zip_uint64_t offset) { + if (ZIP_SOURCE_IS_LAYERED(src)) { + zip_error_set(&src->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } + if (ZIP_SOURCE_IS_OPEN_WRITING(src)) { zip_error_set(&src->error, ZIP_ER_INVAL, 0); return -1; diff --git a/core/deps/libzip/lib/zip_source_buffer.c b/core/deps/libzip/lib/zip_source_buffer.c index a54c4fc84..566729325 100644 --- a/core/deps/libzip/lib/zip_source_buffer.c +++ b/core/deps/libzip/lib/zip_source_buffer.c @@ -1,9 +1,9 @@ /* zip_source_buffer.c -- create zip data source from buffer - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -50,8 +50,8 @@ struct buffer { zip_uint64_t shared_fragments; /* number of shared fragments */ struct buffer *shared_buffer; /* buffer fragments are shared with */ - zip_uint64_t size; /* size of buffer */ + zip_uint64_t size; /* size of buffer */ zip_uint64_t offset; /* current offset in buffer */ zip_uint64_t current_fragment; /* fragment current offset is in */ }; @@ -159,7 +159,7 @@ zip_source_buffer_fragment_with_attributes_create(const zip_buffer_fragment_t *f ctx->out = NULL; ctx->mtime = time(NULL); if (attributes) { - memcpy(&ctx->attributes, attributes, sizeof(ctx->attributes)); + (void)memcpy_s(&ctx->attributes, sizeof(ctx->attributes), attributes, sizeof(ctx->attributes)); } else { zip_file_attributes_init(&ctx->attributes); @@ -226,7 +226,7 @@ read_data(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd) { return -1; } - memcpy(data, &ctx->attributes, sizeof(ctx->attributes)); + (void)memcpy_s(data, sizeof(ctx->attributes), &ctx->attributes, sizeof(ctx->attributes)); return sizeof(ctx->attributes); } @@ -287,7 +287,7 @@ read_data(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd) { } case ZIP_SOURCE_SUPPORTS: - return zip_source_make_command_bitmap(ZIP_SOURCE_GET_FILE_ATTRIBUTES, ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_CLOSE, ZIP_SOURCE_STAT, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, ZIP_SOURCE_SEEK, ZIP_SOURCE_TELL, ZIP_SOURCE_BEGIN_WRITE, ZIP_SOURCE_BEGIN_WRITE_CLONING, ZIP_SOURCE_COMMIT_WRITE, ZIP_SOURCE_REMOVE, ZIP_SOURCE_ROLLBACK_WRITE, ZIP_SOURCE_SEEK_WRITE, ZIP_SOURCE_TELL_WRITE, ZIP_SOURCE_WRITE, -1); + return zip_source_make_command_bitmap(ZIP_SOURCE_GET_FILE_ATTRIBUTES, ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_CLOSE, ZIP_SOURCE_STAT, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, ZIP_SOURCE_SEEK, ZIP_SOURCE_TELL, ZIP_SOURCE_BEGIN_WRITE, ZIP_SOURCE_BEGIN_WRITE_CLONING, ZIP_SOURCE_COMMIT_WRITE, ZIP_SOURCE_REMOVE, ZIP_SOURCE_ROLLBACK_WRITE, ZIP_SOURCE_SEEK_WRITE, ZIP_SOURCE_TELL_WRITE, ZIP_SOURCE_WRITE, ZIP_SOURCE_SUPPORTS_REOPEN, -1); case ZIP_SOURCE_TELL: if (ctx->in->offset > ZIP_INT64_MAX) { @@ -344,6 +344,7 @@ buffer_clone(buffer_t *buffer, zip_uint64_t offset, zip_error_t *error) { fragment_offset = buffer->fragments[fragment].length; } + /* TODO: This should also consider the length of the fully shared fragments */ waste = buffer->fragments[fragment].length - fragment_offset; if (waste > offset) { zip_error_set(error, ZIP_ER_OPNOTSUPP, 0); @@ -356,18 +357,18 @@ buffer_clone(buffer_t *buffer, zip_uint64_t offset, zip_error_t *error) { #ifndef __clang_analyzer__ /* clone->fragments can't be null, since it was created with at least one fragment */ - clone->fragments[clone->nfragments - 1].length = fragment_offset; + clone->fragments[fragment].length = fragment_offset; #endif clone->fragment_offsets[clone->nfragments] = offset; clone->size = offset; - clone->first_owned_fragment = ZIP_MIN(buffer->first_owned_fragment, clone->nfragments - 1); + clone->first_owned_fragment = ZIP_MIN(buffer->first_owned_fragment, clone->nfragments); buffer->shared_buffer = clone; clone->shared_buffer = buffer; - buffer->shared_fragments = clone->nfragments; + buffer->shared_fragments = fragment + 1; clone->shared_fragments = fragment + 1; - + return clone; } @@ -376,6 +377,10 @@ static zip_uint64_t buffer_find_fragment(const buffer_t *buffer, zip_uint64_t offset) { zip_uint64_t low, high, mid; + if (buffer->nfragments == 0) { + return 0; + } + low = 0; high = buffer->nfragments - 1; @@ -429,12 +434,20 @@ buffer_grow_fragments(buffer_t *buffer, zip_uint64_t capacity, zip_error_t *erro return true; } - if ((fragments = realloc(buffer->fragments, sizeof(buffer->fragments[0]) * capacity)) == NULL) { + zip_uint64_t fragments_size = sizeof(buffer->fragments[0]) * capacity; + zip_uint64_t offsets_size = sizeof(buffer->fragment_offsets[0]) * (capacity + 1); + + if (capacity == ZIP_UINT64_MAX || fragments_size < capacity || fragments_size > SIZE_MAX|| offsets_size < capacity || offsets_size > SIZE_MAX) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return false; + } + + if ((fragments = realloc(buffer->fragments, (size_t)fragments_size)) == NULL) { zip_error_set(error, ZIP_ER_MEMORY, 0); return false; } buffer->fragments = fragments; - if ((offsets = realloc(buffer->fragment_offsets, sizeof(buffer->fragment_offsets[0]) * (capacity + 1))) == NULL) { + if ((offsets = realloc(buffer->fragment_offsets, (size_t)offsets_size)) == NULL) { zip_error_set(error, ZIP_ER_MEMORY, 0); return false; } @@ -493,6 +506,7 @@ buffer_new(const zip_buffer_fragment_t *fragments, zip_uint64_t nfragments, int buffer->fragments[j].data = fragments[i].data; buffer->fragments[j].length = fragments[i].length; buffer->fragment_offsets[i] = offset; + /* TODO: overflow */ offset += fragments[i].length; j++; } @@ -523,8 +537,11 @@ buffer_read(buffer_t *buffer, zip_uint8_t *data, zip_uint64_t length) { n = 0; while (n < length) { zip_uint64_t left = ZIP_MIN(length - n, buffer->fragments[i].length - fragment_offset); +#if ZIP_UINT64_MAX > SIZE_MAX + left = ZIP_MIN(left, SIZE_MAX); +#endif - memcpy(data + n, buffer->fragments[i].data + fragment_offset, left); + (void)memcpy_s(data + n, (size_t)left, buffer->fragments[i].data + fragment_offset, (size_t)left); if (left == buffer->fragments[i].length - fragment_offset) { i++; @@ -555,7 +572,7 @@ buffer_seek(buffer_t *buffer, void *data, zip_uint64_t len, zip_error_t *error) static zip_int64_t buffer_write(buffer_t *buffer, const zip_uint8_t *data, zip_uint64_t length, zip_error_t *error) { - zip_uint64_t n, i, fragment_offset, capacity; + zip_uint64_t copied, i, fragment_offset, capacity; if (buffer->offset + length + WRITE_FRAGMENT_SIZE - 1 < length) { zip_error_set(error, ZIP_ER_INVAL, 0); @@ -597,24 +614,30 @@ buffer_write(buffer_t *buffer, const zip_uint8_t *data, zip_uint64_t length, zip i = buffer->current_fragment; fragment_offset = buffer->offset - buffer->fragment_offsets[i]; - n = 0; - while (n < length) { - zip_uint64_t left = ZIP_MIN(length - n, buffer->fragments[i].length - fragment_offset); + copied = 0; + while (copied < length) { + zip_uint64_t n = ZIP_MIN(ZIP_MIN(length - copied, buffer->fragments[i].length - fragment_offset), SIZE_MAX); +#if ZIP_UINT64_MAX > SIZE_MAX + n = ZIP_MIN(n, SIZE_MAX); +#endif - memcpy(buffer->fragments[i].data + fragment_offset, data + n, left); + (void)memcpy_s(buffer->fragments[i].data + fragment_offset, (size_t)n, data + copied, (size_t)n); - if (left == buffer->fragments[i].length - fragment_offset) { + if (n == buffer->fragments[i].length - fragment_offset) { i++; + fragment_offset = 0; } - n += left; - fragment_offset = 0; + else { + fragment_offset += n; + } + copied += n; } - buffer->offset += n; + buffer->offset += copied; buffer->current_fragment = i; if (buffer->offset > buffer->size) { buffer->size = buffer->offset; } - return (zip_int64_t)n; + return (zip_int64_t)copied; } diff --git a/core/deps/libzip/lib/zip_source_call.c b/core/deps/libzip/lib/zip_source_call.c index 66b13d6e4..8c98fc2ef 100644 --- a/core/deps/libzip/lib/zip_source_call.c +++ b/core/deps/libzip/lib/zip_source_call.c @@ -1,9 +1,9 @@ /* zip_source_call.c -- invoke callback command on zip_source - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_close.c b/core/deps/libzip/lib/zip_source_close.c index 264e4b0d3..f4f3ff2b3 100644 --- a/core/deps/libzip/lib/zip_source_close.c +++ b/core/deps/libzip/lib/zip_source_close.c @@ -1,9 +1,9 @@ /* zip_source_close.c -- close zip_source (stop reading) - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_commit_write.c b/core/deps/libzip/lib/zip_source_commit_write.c index ec8cd8bc3..d7f567b80 100644 --- a/core/deps/libzip/lib/zip_source_commit_write.c +++ b/core/deps/libzip/lib/zip_source_commit_write.c @@ -1,9 +1,9 @@ /* zip_source_commit_write.c -- commit changes to file - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,6 +37,11 @@ ZIP_EXTERN int zip_source_commit_write(zip_source_t *src) { + if (ZIP_SOURCE_IS_LAYERED(src)) { + zip_error_set(&src->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } + if (!ZIP_SOURCE_IS_OPEN_WRITING(src)) { zip_error_set(&src->error, ZIP_ER_INVAL, 0); return -1; diff --git a/core/deps/libzip/lib/zip_source_compress.c b/core/deps/libzip/lib/zip_source_compress.c index 35a022cc2..3cf709ffa 100644 --- a/core/deps/libzip/lib/zip_source_compress.c +++ b/core/deps/libzip/lib/zip_source_compress.c @@ -1,9 +1,9 @@ /* zip_source_compress.c -- (de)compression routines - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -83,10 +83,10 @@ static struct implementation implementations[] = { static size_t implementations_size = sizeof(implementations) / sizeof(implementations[0]); -static zip_source_t *compression_source_new(zip_t *za, zip_source_t *src, zip_int32_t method, bool compress, int compression_flags); +static zip_source_t *compression_source_new(zip_t *za, zip_source_t *src, zip_int32_t method, bool compress, zip_uint32_t compression_flags); static zip_int64_t compress_callback(zip_source_t *, void *, void *, zip_uint64_t, zip_source_cmd_t); static void context_free(struct context *ctx); -static struct context *context_new(zip_int32_t method, bool compress, int compression_flags, zip_compression_algorithm_t *algorithm); +static struct context *context_new(zip_int32_t method, bool compress, zip_uint32_t compression_flags, zip_compression_algorithm_t *algorithm); static zip_int64_t compress_read(zip_source_t *, struct context *, void *, zip_uint64_t); zip_compression_algorithm_t * @@ -117,7 +117,7 @@ zip_compression_method_supported(zip_int32_t method, int compress) { } zip_source_t * -zip_source_compress(zip_t *za, zip_source_t *src, zip_int32_t method, int compression_flags) { +zip_source_compress(zip_t *za, zip_source_t *src, zip_int32_t method, zip_uint32_t compression_flags) { return compression_source_new(za, src, method, true, compression_flags); } @@ -128,7 +128,7 @@ zip_source_decompress(zip_t *za, zip_source_t *src, zip_int32_t method) { static zip_source_t * -compression_source_new(zip_t *za, zip_source_t *src, zip_int32_t method, bool compress, int compression_flags) { +compression_source_new(zip_t *za, zip_source_t *src, zip_int32_t method, bool compress, zip_uint32_t compression_flags) { struct context *ctx; zip_source_t *s2; zip_compression_algorithm_t *algorithm = NULL; @@ -158,7 +158,7 @@ compression_source_new(zip_t *za, zip_source_t *src, zip_int32_t method, bool co static struct context * -context_new(zip_int32_t method, bool compress, int compression_flags, zip_compression_algorithm_t *algorithm) { +context_new(zip_int32_t method, bool compress, zip_uint32_t compression_flags, zip_compression_algorithm_t *algorithm) { struct context *ctx; if ((ctx = (struct context *)malloc(sizeof(*ctx))) == NULL) { @@ -240,7 +240,7 @@ compress_read(zip_source_t *src, struct context *ctx, void *data, zip_uint64_t l if (ctx->can_store && (zip_uint64_t)ctx->first_read <= out_offset) { ctx->is_stored = true; ctx->size = (zip_uint64_t)ctx->first_read; - memcpy(data, ctx->buffer, ctx->size); + (void)memcpy_s(data, ctx->size, ctx->buffer, ctx->size); return (zip_int64_t)ctx->size; } end = true; @@ -257,7 +257,7 @@ compress_read(zip_source_t *src, struct context *ctx, void *data, zip_uint64_t l } if ((n = zip_source_read(src, ctx->buffer, sizeof(ctx->buffer))) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); end = true; break; } @@ -319,7 +319,7 @@ compress_callback(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zip ctx->first_read = -1; if (zip_source_stat(src, &st) < 0 || zip_source_get_file_attributes(src, &attributes) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -357,6 +357,7 @@ compress_callback(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zip else { st->comp_method = ZIP_CM_STORE; st->valid |= ZIP_STAT_COMP_METHOD; + st->valid &= ~ZIP_STAT_COMP_SIZE; if (ctx->end_of_stream) { st->size = ctx->size; st->valid |= ZIP_STAT_SIZE; @@ -389,10 +390,9 @@ compress_callback(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zip } case ZIP_SOURCE_SUPPORTS: - return ZIP_SOURCE_SUPPORTS_READABLE | zip_source_make_command_bitmap(ZIP_SOURCE_GET_FILE_ATTRIBUTES, -1); + return ZIP_SOURCE_SUPPORTS_READABLE | zip_source_make_command_bitmap(ZIP_SOURCE_GET_FILE_ATTRIBUTES, ZIP_SOURCE_SUPPORTS_REOPEN, -1); default: - zip_error_set(&ctx->error, ZIP_ER_INTERNAL, 0); - return -1; + return zip_source_pass_to_lower_layer(src, data, len, cmd); } } diff --git a/core/deps/libzip/lib/zip_source_crc.c b/core/deps/libzip/lib/zip_source_crc.c index cae73635a..435a084b7 100644 --- a/core/deps/libzip/lib/zip_source_crc.c +++ b/core/deps/libzip/lib/zip_source_crc.c @@ -1,9 +1,9 @@ /* zip_source_crc.c -- pass-through source that calculates CRC32 and size - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -52,16 +52,16 @@ static zip_int64_t crc_read(zip_source_t *, void *, void *, zip_uint64_t, zip_so zip_source_t * -zip_source_crc(zip_t *za, zip_source_t *src, int validate) { +zip_source_crc_create(zip_source_t *src, int validate, zip_error_t *error) { struct crc_context *ctx; if (src == NULL) { - zip_error_set(&za->error, ZIP_ER_INVAL, 0); + zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } if ((ctx = (struct crc_context *)malloc(sizeof(*ctx))) == NULL) { - zip_error_set(&za->error, ZIP_ER_MEMORY, 0); + zip_error_set(error, ZIP_ER_MEMORY, 0); return NULL; } @@ -72,7 +72,7 @@ zip_source_crc(zip_t *za, zip_source_t *src, int validate) { ctx->crc = (zip_uint32_t)crc32(0, NULL, 0); ctx->size = 0; - return zip_source_layered(za, src, crc_read, ctx); + return zip_source_layered_create(src, crc_read, ctx, error); } @@ -90,7 +90,7 @@ crc_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_source case ZIP_SOURCE_READ: if ((n = zip_source_read(src, data, len)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -103,7 +103,7 @@ crc_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_source struct zip_stat st; if (zip_source_stat(src, &st) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -112,7 +112,8 @@ crc_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_source return -1; } if ((st.valid & ZIP_STAT_SIZE) && st.size != ctx->size) { - zip_error_set(&ctx->error, ZIP_ER_INCONS, 0); + /* We don't have the index here, but the caller should know which file they are reading from. */ + zip_error_set(&ctx->error, ZIP_ER_INCONS, MAKE_DETAIL_WITH_INDEX(ZIP_ER_DETAIL_INVALID_FILE_LENGTH, MAX_DETAIL_INDEX)); return -1; } } @@ -140,6 +141,10 @@ crc_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_source st = (zip_stat_t *)data; if (ctx->crc_complete) { + if ((st->valid & ZIP_STAT_SIZE) && st->size != ctx->size) { + zip_error_set(&ctx->error, ZIP_ER_DATA_LENGTH, 0); + return -1; + } /* TODO: Set comp_size, comp_method, encryption_method? After all, this only works for uncompressed data. */ st->size = ctx->size; @@ -163,11 +168,13 @@ crc_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_source zip_int64_t mask = zip_source_supports(src); if (mask < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } - return mask & ~zip_source_make_command_bitmap(ZIP_SOURCE_BEGIN_WRITE, ZIP_SOURCE_COMMIT_WRITE, ZIP_SOURCE_ROLLBACK_WRITE, ZIP_SOURCE_SEEK_WRITE, ZIP_SOURCE_TELL_WRITE, ZIP_SOURCE_REMOVE, ZIP_SOURCE_GET_FILE_ATTRIBUTES, -1); + mask &= ~zip_source_make_command_bitmap(ZIP_SOURCE_BEGIN_WRITE, ZIP_SOURCE_COMMIT_WRITE, ZIP_SOURCE_ROLLBACK_WRITE, ZIP_SOURCE_SEEK_WRITE, ZIP_SOURCE_TELL_WRITE, ZIP_SOURCE_REMOVE, ZIP_SOURCE_GET_FILE_ATTRIBUTES, -1); + mask |= zip_source_make_command_bitmap(ZIP_SOURCE_FREE, -1); + return mask; } case ZIP_SOURCE_SEEK: { @@ -178,7 +185,7 @@ crc_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_source return -1; } if (zip_source_seek(src, args->offset, args->whence) < 0 || (new_position = zip_source_tell(src)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -191,7 +198,6 @@ crc_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_source return (zip_int64_t)ctx->position; default: - zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0); - return -1; + return zip_source_pass_to_lower_layer(src, data, len, cmd); } } diff --git a/core/deps/libzip/lib/zip_source_error.c b/core/deps/libzip/lib/zip_source_error.c index 00998e839..dc7fa20cb 100644 --- a/core/deps/libzip/lib/zip_source_error.c +++ b/core/deps/libzip/lib/zip_source_error.c @@ -1,9 +1,9 @@ /* zip_source_error.c -- get last error from zip_source - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_file.h b/core/deps/libzip/lib/zip_source_file.h index 43a464533..cca9fd2b5 100644 --- a/core/deps/libzip/lib/zip_source_file.h +++ b/core/deps/libzip/lib/zip_source_file.h @@ -1,9 +1,9 @@ /* zip_source_file.h -- header for common file operations - Copyright (C) 2020 Dieter Baron and Thomas Klausner + Copyright (C) 2020-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -34,7 +34,7 @@ struct zip_source_file_stat { zip_uint64_t size; /* must be valid for regular files */ time_t mtime; /* must always be valid, is initialized to current time */ - bool exists; /* must always be vaild */ + bool exists; /* must always be valid */ bool regular_file; /* must always be valid */ }; diff --git a/core/deps/libzip/lib/zip_source_file_common.c b/core/deps/libzip/lib/zip_source_file_common.c index 32414361b..6c58320f1 100644 --- a/core/deps/libzip/lib/zip_source_file_common.c +++ b/core/deps/libzip/lib/zip_source_file_common.c @@ -1,9 +1,9 @@ /* zip_source_file_common.c -- create data source from file - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -54,6 +54,7 @@ zip_source_file_common_new(const char *fname, void *file, zip_uint64_t start, zi zip_source_file_context_t *ctx; zip_source_t *zs; zip_source_file_stat_t sb; + zip_uint64_t length; if (ops == NULL) { zip_error_set(error, ZIP_ER_INVAL, 0); @@ -82,10 +83,17 @@ zip_source_file_common_new(const char *fname, void *file, zip_uint64_t start, zi } if (len < 0) { - len = 0; + if (len == -1) { + len = ZIP_LENGTH_TO_END; + } + // TODO: return ZIP_ER_INVAL if len != ZIP_LENGTH_UNCHECKED? + length = 0; + } + else { + length = (zip_uint64_t)len; } - if (start > ZIP_INT64_MAX || start + (zip_uint64_t)len < start) { + if (start > ZIP_INT64_MAX || start + length < start) { zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } @@ -107,9 +115,9 @@ zip_source_file_common_new(const char *fname, void *file, zip_uint64_t start, zi } ctx->f = file; ctx->start = start; - ctx->len = (zip_uint64_t)len; + ctx->len = length; if (st) { - memcpy(&ctx->st, st, sizeof(ctx->st)); + (void)memcpy_s(&ctx->st, sizeof(ctx->st), st, sizeof(*st)); ctx->st.name = NULL; ctx->st.valid &= ~ZIP_STAT_NAME; } @@ -130,7 +138,7 @@ zip_source_file_common_new(const char *fname, void *file, zip_uint64_t start, zi zip_error_init(&ctx->error); zip_file_attributes_init(&ctx->attributes); - ctx->supports = ZIP_SOURCE_SUPPORTS_READABLE | zip_source_make_command_bitmap(ZIP_SOURCE_SUPPORTS, ZIP_SOURCE_TELL, -1); + ctx->supports = ZIP_SOURCE_SUPPORTS_READABLE | zip_source_make_command_bitmap(ZIP_SOURCE_SUPPORTS, ZIP_SOURCE_TELL, ZIP_SOURCE_SUPPORTS_REOPEN, -1); zip_source_file_stat_init(&sb); if (!ops->stat(ctx, &sb)) { @@ -169,9 +177,11 @@ zip_source_file_common_new(const char *fname, void *file, zip_uint64_t start, zi } if (ctx->len == 0) { - ctx->len = sb.size - ctx->start; - ctx->st.size = ctx->len; - ctx->st.valid |= ZIP_STAT_SIZE; + if (len != ZIP_LENGTH_UNCHECKED) { + ctx->len = sb.size - ctx->start; + ctx->st.size = ctx->len; + ctx->st.valid |= ZIP_STAT_SIZE; + } /* when using a partial file, don't allow writing */ if (ctx->fname && start == 0 && ops->write != NULL) { @@ -262,7 +272,7 @@ read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd) { zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); return -1; } - memcpy(data, &ctx->attributes, sizeof(ctx->attributes)); + (void)memcpy_s(data, sizeof(ctx->attributes), &ctx->attributes, sizeof(ctx->attributes)); return sizeof(ctx->attributes); case ZIP_SOURCE_OPEN: @@ -272,7 +282,7 @@ read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd) { } } - if (ctx->start > 0) { // TODO: rewind on re-open + if (ctx->start > 0) { /* TODO: rewind on re-open */ if (ctx->ops->seek(ctx, ctx->f, (zip_int64_t)ctx->start, SEEK_SET) == false) { /* TODO: skip by reading */ return -1; @@ -355,7 +365,7 @@ read_file(void *state, void *data, zip_uint64_t len, zip_source_cmd_t cmd) { return -1; } - memcpy(data, &ctx->st, sizeof(ctx->st)); + (void)memcpy_s(data, sizeof(ctx->st), &ctx->st, sizeof(ctx->st)); return sizeof(ctx->st); } diff --git a/core/deps/libzip/lib/zip_source_file_stdio.c b/core/deps/libzip/lib/zip_source_file_stdio.c index 15817716c..6dcc56399 100644 --- a/core/deps/libzip/lib/zip_source_file_stdio.c +++ b/core/deps/libzip/lib/zip_source_file_stdio.c @@ -3,7 +3,7 @@ Copyright (C) 2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -78,7 +78,7 @@ zip_source_filep(zip_t *za, FILE *file, zip_uint64_t start, zip_int64_t len) { ZIP_EXTERN zip_source_t * zip_source_filep_create(FILE *file, zip_uint64_t start, zip_int64_t length, zip_error_t *error) { - if (file == NULL || length < -1) { + if (file == NULL || length < ZIP_LENGTH_UNCHECKED) { zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } @@ -176,33 +176,3 @@ _zip_stdio_op_tell(zip_source_file_context_t *ctx, void *f) { return offset; } - - -/* - * fopen replacement that sets the close-on-exec flag - * some implementations support an fopen 'e' flag for that, - * but e.g. macOS doesn't. - */ -FILE * -_zip_fopen_close_on_exec(const char *name, bool writeable) { - int fd; - int flags; - FILE *fp; - - flags = O_CLOEXEC; - if (writeable) { - flags |= O_RDWR; - } - else { - flags |= O_RDONLY; - } - - /* mode argument needed on Windows */ - if ((fd = open(name, flags, 0666)) < 0) { - return NULL; - } - if ((fp = fdopen(fd, writeable ? "r+b" : "rb")) == NULL) { - return NULL; - } - return fp; -} diff --git a/core/deps/libzip/lib/zip_source_file_stdio.h b/core/deps/libzip/lib/zip_source_file_stdio.h index 1bf698ce2..802e6071f 100644 --- a/core/deps/libzip/lib/zip_source_file_stdio.h +++ b/core/deps/libzip/lib/zip_source_file_stdio.h @@ -6,7 +6,7 @@ Copyright (C) 2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -42,6 +42,4 @@ bool _zip_stdio_op_seek(zip_source_file_context_t *ctx, void *f, zip_int64_t off bool _zip_stdio_op_stat(zip_source_file_context_t *ctx, zip_source_file_stat_t *st); zip_int64_t _zip_stdio_op_tell(zip_source_file_context_t *ctx, void *f); -FILE *_zip_fopen_close_on_exec(const char *name, bool writeable); - #endif /* _HAD_ZIP_SOURCE_FILE_STDIO_H */ diff --git a/core/deps/libzip/lib/zip_source_file_stdio_named.c b/core/deps/libzip/lib/zip_source_file_stdio_named.c index 5387c7663..1495d7dde 100644 --- a/core/deps/libzip/lib/zip_source_file_stdio_named.c +++ b/core/deps/libzip/lib/zip_source_file_stdio_named.c @@ -1,9 +1,9 @@ /* zip_source_file_stdio_named.c -- source for stdio file opened by name - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -36,6 +36,7 @@ #include "zip_source_file.h" #include "zip_source_file_stdio.h" +#include #include #include #ifdef HAVE_UNISTD_H @@ -53,6 +54,8 @@ #define CAN_CLONE #endif +static int create_temp_file(zip_source_file_context_t *ctx, bool create_file); + static zip_int64_t _zip_stdio_op_commit_write(zip_source_file_context_t *ctx); static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx); #ifdef CAN_CLONE @@ -63,6 +66,7 @@ static zip_int64_t _zip_stdio_op_remove(zip_source_file_context_t *ctx); static void _zip_stdio_op_rollback_write(zip_source_file_context_t *ctx); static char *_zip_stdio_op_strdup(zip_source_file_context_t *ctx, const char *string); static zip_int64_t _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64_t len); +static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable); /* clang-format off */ static zip_source_file_operations_t ops_stdio_named = { @@ -97,7 +101,7 @@ zip_source_file(zip_t *za, const char *fname, zip_uint64_t start, zip_int64_t le ZIP_EXTERN zip_source_t * zip_source_file_create(const char *fname, zip_uint64_t start, zip_int64_t length, zip_error_t *error) { - if (fname == NULL || length < -1) { + if (fname == NULL || length < ZIP_LENGTH_UNCHECKED) { zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } @@ -123,82 +127,51 @@ _zip_stdio_op_commit_write(zip_source_file_context_t *ctx) { static zip_int64_t _zip_stdio_op_create_temp_output(zip_source_file_context_t *ctx) { - char *temp; - int tfd; - int mode; - FILE *tfp; - struct stat st; - - if ((temp = (char *)malloc(strlen(ctx->fname) + 8)) == NULL) { - zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0); + int fd = create_temp_file(ctx, true); + + if (fd < 0) { return -1; } - - if (stat(ctx->fname, &st) == 0) { - mode = st.st_mode; - } - else { - mode = -1; - } - - sprintf(temp, "%s.XXXXXX", ctx->fname); - - if ((tfd = _zip_mkstempm(temp, mode)) == -1) { + + if ((ctx->fout = fdopen(fd, "r+b")) == NULL) { zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); - free(temp); + close(fd); + (void)remove(ctx->tmpname); + free(ctx->tmpname); + ctx->tmpname = NULL; return -1; } - if ((tfp = fdopen(tfd, "r+b")) == NULL) { - zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); - close(tfd); - (void)remove(temp); - free(temp); - return -1; - } - - ctx->fout = tfp; - ctx->tmpname = temp; - return 0; } #ifdef CAN_CLONE static zip_int64_t _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uint64_t offset) { - char *temp; FILE *tfp; - + if (offset > ZIP_OFF_MAX) { zip_error_set(&ctx->error, ZIP_ER_SEEK, E2BIG); return -1; } - - if ((temp = (char *)malloc(strlen(ctx->fname) + 8)) == NULL) { - zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0); - return -1; - } - sprintf(temp, "%s.XXXXXX", ctx->fname); - + #ifdef HAVE_CLONEFILE -#ifndef __clang_analyzer__ - /* we can't use mkstemp, since clonefile insists on creating the file */ - if (mktemp(temp) == NULL) { - zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); - free(temp); + /* clonefile insists on creating the file, so just create a name */ + if (create_temp_file(ctx, false) < 0) { return -1; } -#endif - - if (clonefile(ctx->fname, temp, 0) < 0) { + + if (clonefile(ctx->fname, ctx->tmpname, 0) < 0) { zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); - free(temp); + free(ctx->tmpname); + ctx->tmpname = NULL; return -1; } - if ((tfp = _zip_fopen_close_on_exec(temp, true)) == NULL) { + if ((tfp = _zip_fopen_close_on_exec(ctx->tmpname, true)) == NULL) { zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); - (void)remove(temp); - free(temp); + (void)remove(ctx->tmpname); + free(ctx->tmpname); + ctx->tmpname = NULL; return -1; } #else @@ -206,19 +179,16 @@ _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uin int fd; struct file_clone_range range; struct stat st; - + if (fstat(fileno(ctx->f), &st) < 0) { zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); - free(temp); return -1; } - - if ((fd = mkstemp(temp)) < 0) { - zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); - free(temp); + + if ((fd = create_temp_file(ctx, true)) < 0) { return -1; } - + range.src_fd = fileno(ctx->f); range.src_offset = 0; range.src_length = ((offset + st.st_blksize - 1) / st.st_blksize) * st.st_blksize; @@ -229,16 +199,18 @@ _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uin if (ioctl(fd, FICLONERANGE, &range) < 0) { zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); (void)close(fd); - (void)remove(temp); - free(temp); + (void)remove(ctx->tmpname); + free(ctx->tmpname); + ctx->tmpname = NULL; return -1; } if ((tfp = fdopen(fd, "r+b")) == NULL) { zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); (void)close(fd); - (void)remove(temp); - free(temp); + (void)remove(ctx->tmpname); + free(ctx->tmpname); + ctx->tmpname = NULL; return -1; } } @@ -246,19 +218,21 @@ _zip_stdio_op_create_temp_output_cloning(zip_source_file_context_t *ctx, zip_uin if (ftruncate(fileno(tfp), (off_t)offset) < 0) { (void)fclose(tfp); - (void)remove(temp); - free(temp); + (void)remove(ctx->tmpname); + free(ctx->tmpname); + ctx->tmpname = NULL; return -1; } if (fseeko(tfp, (off_t)offset, SEEK_SET) < 0) { - (void)fclose(tfp); - (void)remove(temp); - free(temp); zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); + (void)fclose(tfp); + (void)remove(ctx->tmpname); + free(ctx->tmpname); + ctx->tmpname = NULL; + return -1; } ctx->fout = tfp; - ctx->tmpname = temp; return 0; } @@ -311,3 +285,108 @@ _zip_stdio_op_write(zip_source_file_context_t *ctx, const void *data, zip_uint64 return (zip_int64_t)ret; } + + +static int create_temp_file(zip_source_file_context_t *ctx, bool create_file) { + char *temp; + int mode; + struct stat st; + int fd = 0; + char *start, *end; + + if (stat(ctx->fname, &st) == 0) { + mode = st.st_mode; + } + else { + mode = -1; + } + + size_t temp_size = strlen(ctx->fname) + 13; + if ((temp = (char *)malloc(temp_size)) == NULL) { + zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0); + return -1; + } + snprintf_s(temp, temp_size, "%s.XXXXXX.part", ctx->fname); + end = temp + strlen(temp) - 5; + start = end - 6; + + for (;;) { + zip_uint32_t value = zip_random_uint32(); + char *xs = start; + + while (xs < end) { + char digit = value % 36; + if (digit < 10) { + *(xs++) = digit + '0'; + } + else { + *(xs++) = digit - 10 + 'a'; + } + value /= 36; + } + + if (create_file) { + if ((fd = open(temp, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, mode == -1 ? 0666 : (mode_t)mode)) >= 0) { + if (mode != -1) { + /* open() honors umask(), which we don't want in this case */ +#ifdef HAVE_FCHMOD + (void)fchmod(fd, (mode_t)mode); +#else + (void)chmod(temp, (mode_t)mode); +#endif + } + break; + } + if (errno != EEXIST) { + zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); + free(temp); + return -1; + } + } + else { + if (stat(temp, &st) < 0) { + if (errno == ENOENT) { + break; + } + else { + zip_error_set(&ctx->error, ZIP_ER_TMPOPEN, errno); + free(temp); + return -1; + } + } + } + } + + ctx->tmpname = temp; + + return fd; /* initialized to 0 if !create_file */ +} + + +/* + * fopen replacement that sets the close-on-exec flag + * some implementations support an fopen 'e' flag for that, + * but e.g. macOS doesn't. + */ +static FILE *_zip_fopen_close_on_exec(const char *name, bool writeable) { + int fd; + int flags; + FILE *fp; + + flags = O_CLOEXEC; + if (writeable) { + flags |= O_RDWR; + } + else { + flags |= O_RDONLY; + } + + /* mode argument needed on Windows */ + if ((fd = open(name, flags, 0666)) < 0) { + return NULL; + } + if ((fp = fdopen(fd, writeable ? "r+b" : "rb")) == NULL) { + return NULL; + } + return fp; +} diff --git a/core/deps/libzip/lib/zip_source_file_win32.c b/core/deps/libzip/lib/zip_source_file_win32.c index 6547fc20e..624860b1c 100644 --- a/core/deps/libzip/lib/zip_source_file_win32.c +++ b/core/deps/libzip/lib/zip_source_file_win32.c @@ -1,9 +1,9 @@ /* zip_source_file_win32.c -- read-only Windows file source implementation - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -69,7 +69,7 @@ zip_source_win32handle(zip_t *za, HANDLE h, zip_uint64_t start, zip_int64_t len) ZIP_EXTERN zip_source_t * zip_source_win32handle_create(HANDLE h, zip_uint64_t start, zip_int64_t length, zip_error_t *error) { - if (h == INVALID_HANDLE_VALUE || length < -1) { + if (h == INVALID_HANDLE_VALUE || length < ZIP_LENGTH_UNCHECKED) { zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } @@ -115,7 +115,7 @@ _zip_win32_op_seek(zip_source_file_context_t *ctx, void *f, zip_int64_t offset, break; default: zip_error_set(&ctx->error, ZIP_ER_SEEK, EINVAL); - return -1; + return false; } li.QuadPart = (LONGLONG)offset; @@ -184,7 +184,7 @@ _zip_stat_win32(zip_source_file_context_t *ctx, zip_source_file_stat_t *st, HAND zip_error_set(&ctx->error, ZIP_ER_READ, _zip_win32_error_to_errno(GetLastError())); return false; } - if (_zip_filetime_to_time_t(mtimeft, &mtime) < 0) { + if (!_zip_filetime_to_time_t(mtimeft, &mtime)) { zip_error_set(&ctx->error, ZIP_ER_READ, ERANGE); return false; } diff --git a/core/deps/libzip/lib/zip_source_file_win32.h b/core/deps/libzip/lib/zip_source_file_win32.h index 88e45b1f9..d86069eca 100644 --- a/core/deps/libzip/lib/zip_source_file_win32.h +++ b/core/deps/libzip/lib/zip_source_file_win32.h @@ -6,7 +6,7 @@ Copyright (C) 2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -73,4 +73,12 @@ zip_int64_t _zip_win32_op_tell(zip_source_file_context_t *ctx, void *f); bool _zip_filetime_to_time_t(FILETIME ft, time_t *t); int _zip_win32_error_to_errno(DWORD win32err); +#ifdef __clang__ +#define DONT_WARN_INCOMPATIBLE_FN_PTR_BEGIN _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wincompatible-function-pointer-types\"") +#define DONT_WARN_INCOMPATIBLE_FN_PTR_END _Pragma("GCC diagnostic pop") +#else +#define DONT_WARN_INCOMPATIBLE_FN_PTR_BEGIN +#define DONT_WARN_INCOMPATIBLE_FN_PTR_END +#endif + #endif /* _HAD_ZIP_SOURCE_FILE_WIN32_H */ diff --git a/core/deps/libzip/lib/zip_source_file_win32_ansi.c b/core/deps/libzip/lib/zip_source_file_win32_ansi.c index f299692b8..58034cc2a 100644 --- a/core/deps/libzip/lib/zip_source_file_win32_ansi.c +++ b/core/deps/libzip/lib/zip_source_file_win32_ansi.c @@ -3,7 +3,7 @@ Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,6 +37,7 @@ static char *ansi_allocate_tempname(const char *name, size_t extra_chars, size_t static void ansi_make_tempname(char *buf, size_t len, const char *name, zip_uint32_t i); /* clang-format off */ +DONT_WARN_INCOMPATIBLE_FN_PTR_BEGIN zip_win32_file_operations_t ops_ansi = { ansi_allocate_tempname, @@ -50,6 +51,7 @@ zip_win32_file_operations_t ops_ansi = { strdup }; +DONT_WARN_INCOMPATIBLE_FN_PTR_END /* clang-format on */ ZIP_EXTERN zip_source_t * @@ -63,7 +65,7 @@ zip_source_win32a(zip_t *za, const char *fname, zip_uint64_t start, zip_int64_t ZIP_EXTERN zip_source_t * zip_source_win32a_create(const char *fname, zip_uint64_t start, zip_int64_t length, zip_error_t *error) { - if (fname == NULL || length < -1) { + if (fname == NULL || length < ZIP_LENGTH_UNCHECKED) { zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } @@ -81,5 +83,5 @@ ansi_allocate_tempname(const char *name, size_t extra_chars, size_t *lengthp) { static void ansi_make_tempname(char *buf, size_t len, const char *name, zip_uint32_t i) { - snprintf(buf, len, "%s.%08x", name, i); + snprintf_s(buf, len, "%s.%08x", name, i); } diff --git a/core/deps/libzip/lib/zip_source_file_win32_named.c b/core/deps/libzip/lib/zip_source_file_win32_named.c index 1fe559180..855e605a9 100644 --- a/core/deps/libzip/lib/zip_source_file_win32_named.c +++ b/core/deps/libzip/lib/zip_source_file_win32_named.c @@ -3,7 +3,7 @@ Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -99,7 +99,6 @@ _zip_win32_named_op_create_temp_output(zip_source_file_context_t *ctx) { zip_uint32_t value, i; HANDLE th = INVALID_HANDLE_VALUE; - void *temp = NULL; PSECURITY_DESCRIPTOR psd = NULL; PSECURITY_ATTRIBUTES psa = NULL; SECURITY_ATTRIBUTES sa; diff --git a/core/deps/libzip/lib/zip_source_file_win32_utf16.c b/core/deps/libzip/lib/zip_source_file_win32_utf16.c index 6aef2bb49..8f07d0210 100644 --- a/core/deps/libzip/lib/zip_source_file_win32_utf16.c +++ b/core/deps/libzip/lib/zip_source_file_win32_utf16.c @@ -3,7 +3,7 @@ Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -39,6 +39,7 @@ static void utf16_make_tempname(char *buf, size_t len, const char *name, zip_uin static char *utf16_strdup(const char *string); /* clang-format off */ +DONT_WARN_INCOMPATIBLE_FN_PTR_BEGIN zip_win32_file_operations_t ops_utf16 = { utf16_allocate_tempname, @@ -52,6 +53,7 @@ zip_win32_file_operations_t ops_utf16 = { utf16_strdup }; +DONT_WARN_INCOMPATIBLE_FN_PTR_END /* clang-format on */ ZIP_EXTERN zip_source_t * @@ -65,7 +67,7 @@ zip_source_win32w(zip_t *za, const wchar_t *fname, zip_uint64_t start, zip_int64 ZIP_EXTERN zip_source_t * zip_source_win32w_create(const wchar_t *fname, zip_uint64_t start, zip_int64_t length, zip_error_t *error) { - if (fname == NULL || length < -1) { + if (fname == NULL || length < ZIP_LENGTH_UNCHECKED) { zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } @@ -101,7 +103,7 @@ static HANDLE __stdcall utf16_create_file(const char *name, DWORD access, DWORD static void utf16_make_tempname(char *buf, size_t len, const char *name, zip_uint32_t i) { - _snwprintf((wchar_t *)buf, len, L"%s.%08x", (const wchar_t *)name, i); + _snwprintf_s((wchar_t *)buf, len, len, L"%s.%08x", (const wchar_t *)name, i); } diff --git a/core/deps/libzip/lib/zip_source_file_win32_utf8.c b/core/deps/libzip/lib/zip_source_file_win32_utf8.c index d6728e34f..d154f97ae 100644 --- a/core/deps/libzip/lib/zip_source_file_win32_utf8.c +++ b/core/deps/libzip/lib/zip_source_file_win32_utf8.c @@ -3,7 +3,7 @@ Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -49,7 +49,7 @@ zip_source_file_create(const char *fname, zip_uint64_t start, zip_int64_t length wchar_t *wfname; zip_source_t *source; - if (fname == NULL || length < -1) { + if (fname == NULL || length < ZIP_LENGTH_UNCHECKED) { zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } diff --git a/core/deps/libzip/lib/zip_source_free.c b/core/deps/libzip/lib/zip_source_free.c index 26504931e..1a800405c 100644 --- a/core/deps/libzip/lib/zip_source_free.c +++ b/core/deps/libzip/lib/zip_source_free.c @@ -1,9 +1,9 @@ /* zip_source_free.c -- free zip data source - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_function.c b/core/deps/libzip/lib/zip_source_function.c index 5602f1d19..1fe6396e1 100644 --- a/core/deps/libzip/lib/zip_source_function.c +++ b/core/deps/libzip/lib/zip_source_function.c @@ -1,9 +1,9 @@ /* zip_source_function.c -- create zip data source from callback function - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -61,6 +61,7 @@ zip_source_function_create(zip_source_callback zcb, void *ud, zip_error_t *error if (zs->supports < 0) { zs->supports = ZIP_SOURCE_SUPPORTS_READABLE; } + zs->supports |= zip_source_make_command_bitmap(ZIP_SOURCE_SUPPORTS, -1); return zs; } diff --git a/core/deps/libzip/lib/zip_source_get_file_attributes.c b/core/deps/libzip/lib/zip_source_get_file_attributes.c index 734767a0e..4771dc16b 100644 --- a/core/deps/libzip/lib/zip_source_get_file_attributes.c +++ b/core/deps/libzip/lib/zip_source_get_file_attributes.c @@ -3,7 +3,7 @@ Copyright (C) 2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -60,8 +60,10 @@ zip_source_get_file_attributes(zip_source_t *src, zip_file_attributes_t *attribu if (ZIP_SOURCE_IS_LAYERED(src)) { zip_file_attributes_t lower_attributes; + zip_file_attributes_init(&lower_attributes); + if (zip_source_get_file_attributes(src->src, &lower_attributes) < 0) { - _zip_error_set_from_source(&src->error, src->src); + zip_error_set_from_source(&src->error, src->src); return -1; } diff --git a/core/deps/libzip/lib/zip_source_is_deleted.c b/core/deps/libzip/lib/zip_source_is_deleted.c index 27c931fd4..838aa9096 100644 --- a/core/deps/libzip/lib/zip_source_is_deleted.c +++ b/core/deps/libzip/lib/zip_source_is_deleted.c @@ -1,9 +1,9 @@ /* zip_source_is_deleted.c -- was archive was removed? - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_layered.c b/core/deps/libzip/lib/zip_source_layered.c index a02986929..62b78e68e 100644 --- a/core/deps/libzip/lib/zip_source_layered.c +++ b/core/deps/libzip/lib/zip_source_layered.c @@ -1,9 +1,9 @@ /* zip_source_layered.c -- create layered source - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -49,19 +49,27 @@ zip_source_layered(zip_t *za, zip_source_t *src, zip_source_layered_callback cb, zip_source_t * zip_source_layered_create(zip_source_t *src, zip_source_layered_callback cb, void *ud, zip_error_t *error) { zip_source_t *zs; + zip_int64_t lower_supports, supports; - if ((zs = _zip_source_new(error)) == NULL) + lower_supports = zip_source_supports(src); + supports = cb(src, ud, &lower_supports, sizeof(lower_supports), ZIP_SOURCE_SUPPORTS); + if (supports < 0) { + zip_error_set(error,ZIP_ER_INVAL, 0); /* Initialize in case cb doesn't return valid error. */ + cb(src, ud, error, sizeof(*error), ZIP_SOURCE_ERROR); return NULL; + } + + if ((zs = _zip_source_new(error)) == NULL) { + return NULL; + } - zip_source_keep(src); zs->src = src; zs->cb.l = cb; zs->ud = ud; + zs->supports = supports; - zs->supports = cb(src, ud, NULL, 0, ZIP_SOURCE_SUPPORTS); - if (zs->supports < 0) { - zs->supports = ZIP_SOURCE_SUPPORTS_READABLE; - } + /* Layered sources can't support writing, since we currently have no use case. If we want to revisit this, we have to define how the two sources interact. */ + zs->supports &= ~(ZIP_SOURCE_SUPPORTS_WRITABLE & ~ZIP_SOURCE_SUPPORTS_SEEKABLE); return zs; } diff --git a/core/deps/libzip/lib/zip_source_open.c b/core/deps/libzip/lib/zip_source_open.c index 442f1894f..b34fa2c6c 100644 --- a/core/deps/libzip/lib/zip_source_open.c +++ b/core/deps/libzip/lib/zip_source_open.c @@ -1,9 +1,9 @@ /* zip_source_open.c -- open zip_source (prepare for reading) - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -53,7 +53,7 @@ zip_source_open(zip_source_t *src) { else { if (ZIP_SOURCE_IS_LAYERED(src)) { if (zip_source_open(src->src) < 0) { - _zip_error_set_from_source(&src->error, src->src); + zip_error_set_from_source(&src->error, src->src); return -1; } } diff --git a/core/deps/libzip/lib/zip_mkstempm.c b/core/deps/libzip/lib/zip_source_pass_to_lower_layer.c similarity index 53% rename from core/deps/libzip/lib/zip_mkstempm.c rename to core/deps/libzip/lib/zip_source_pass_to_lower_layer.c index 41516d2ff..4a98222ec 100644 --- a/core/deps/libzip/lib/zip_mkstempm.c +++ b/core/deps/libzip/lib/zip_source_pass_to_lower_layer.c @@ -1,9 +1,9 @@ /* - zip_mkstempm.c -- mkstemp replacement that accepts a mode argument - Copyright (C) 2019-2020 Dieter Baron and Thomas Klausner + zip_source_pass_to_lower_layer.c -- pass command to lower layer + Copyright (C) 2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -31,63 +31,48 @@ IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include -#include -#include - #include "zipint.h" -/* - * create temporary file with same permissions as previous one; - * or default permissions if there is no previous file - */ -int -_zip_mkstempm(char *path, int mode) { - int fd; - char *start, *end, *xs; +zip_int64_t zip_source_pass_to_lower_layer(zip_source_t *src, void *data, zip_uint64_t length, zip_source_cmd_t command) { + switch (command) { + case ZIP_SOURCE_OPEN: + case ZIP_SOURCE_CLOSE: + case ZIP_SOURCE_FREE: + case ZIP_SOURCE_GET_FILE_ATTRIBUTES: + case ZIP_SOURCE_SUPPORTS_REOPEN: + return 0; - int xcnt = 0; + case ZIP_SOURCE_STAT: + return sizeof(zip_stat_t); - end = path + strlen(path); - start = end - 1; - while (start >= path && *start == 'X') { - xcnt++; - start--; - } + case ZIP_SOURCE_ACCEPT_EMPTY: + case ZIP_SOURCE_ERROR: + case ZIP_SOURCE_READ: + case ZIP_SOURCE_SEEK: + case ZIP_SOURCE_TELL: + return _zip_source_call(src, data, length, command); - if (xcnt == 0) { - errno = EINVAL; + + case ZIP_SOURCE_BEGIN_WRITE: + case ZIP_SOURCE_BEGIN_WRITE_CLONING: + case ZIP_SOURCE_COMMIT_WRITE: + case ZIP_SOURCE_REMOVE: + case ZIP_SOURCE_ROLLBACK_WRITE: + case ZIP_SOURCE_SEEK_WRITE: + case ZIP_SOURCE_TELL_WRITE: + case ZIP_SOURCE_WRITE: + zip_error_set(&src->error, ZIP_ER_OPNOTSUPP, 0); return -1; - } - start++; - - for (;;) { - zip_uint32_t value = zip_random_uint32(); - - xs = start; - - while (xs < end) { - char digit = value % 36; - if (digit < 10) { - *(xs++) = digit + '0'; - } - else { - *(xs++) = digit - 10 + 'a'; - } - value /= 36; - } - - if ((fd = open(path, O_CREAT | O_EXCL | O_RDWR | O_CLOEXEC, mode == -1 ? 0666 : (mode_t)mode)) >= 0) { - if (mode != -1) { - /* open() honors umask(), which we don't want in this case */ - (void)chmod(path, (mode_t)mode); - } - return fd; - } - if (errno != EEXIST) { + case ZIP_SOURCE_SUPPORTS: + if (length < sizeof(zip_int64_t)) { + zip_error_set(&src->error, ZIP_ER_INTERNAL, 0); return -1; } + return *(zip_int64_t *)data; + + default: + zip_error_set(&src->error, ZIP_ER_OPNOTSUPP, 0); + return -1; } -} +} \ No newline at end of file diff --git a/core/deps/libzip/lib/zip_source_pkware_decode.c b/core/deps/libzip/lib/zip_source_pkware_decode.c index a1c9e3a9b..b4c482b31 100644 --- a/core/deps/libzip/lib/zip_source_pkware_decode.c +++ b/core/deps/libzip/lib/zip_source_pkware_decode.c @@ -3,7 +3,7 @@ Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -85,7 +85,7 @@ decrypt_header(zip_source_t *src, struct trad_pkware *ctx) { bool ok = false; if ((n = zip_source_read(src, header, ZIP_CRYPTO_PKWARE_HEADERLEN)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -147,7 +147,7 @@ pkware_decrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zip_so case ZIP_SOURCE_READ: if ((n = zip_source_read(src, data, len)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -172,7 +172,7 @@ pkware_decrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zip_so } case ZIP_SOURCE_SUPPORTS: - return zip_source_make_command_bitmap(ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_CLOSE, ZIP_SOURCE_STAT, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, -1); + return zip_source_make_command_bitmap(ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_CLOSE, ZIP_SOURCE_STAT, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, ZIP_SOURCE_SUPPORTS_REOPEN, -1); case ZIP_SOURCE_ERROR: return zip_error_to_data(&ctx->error, data, len); @@ -182,8 +182,7 @@ pkware_decrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zip_so return 0; default: - zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); - return -1; + return zip_source_pass_to_lower_layer(src, data, len, cmd); } } diff --git a/core/deps/libzip/lib/zip_source_pkware_encode.c b/core/deps/libzip/lib/zip_source_pkware_encode.c index 7c77e1e86..d89b9f4e8 100644 --- a/core/deps/libzip/lib/zip_source_pkware_encode.c +++ b/core/deps/libzip/lib/zip_source_pkware_encode.c @@ -3,7 +3,7 @@ Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -42,6 +42,8 @@ struct trad_pkware { zip_pkware_keys_t keys; zip_buffer_t *buffer; bool eof; + bool mtime_set; + time_t mtime; zip_error_t error; }; @@ -50,7 +52,7 @@ static int encrypt_header(zip_source_t *, struct trad_pkware *); static zip_int64_t pkware_encrypt(zip_source_t *, void *, void *, zip_uint64_t, zip_source_cmd_t); static void trad_pkware_free(struct trad_pkware *); static struct trad_pkware *trad_pkware_new(const char *password, zip_error_t *error); - +static void set_mtime(struct trad_pkware* ctx, zip_stat_t* st); zip_source_t * zip_source_pkware_encode(zip_t *za, zip_source_t *src, zip_uint16_t em, int flags, const char *password) { @@ -81,16 +83,19 @@ zip_source_pkware_encode(zip_t *za, zip_source_t *src, zip_uint16_t em, int flag static int encrypt_header(zip_source_t *src, struct trad_pkware *ctx) { - struct zip_stat st; unsigned short dostime, dosdate; zip_uint8_t *header; - if (zip_source_stat(src, &st) != 0) { - _zip_error_set_from_source(&ctx->error, src); - return -1; + if (!ctx->mtime_set) { + struct zip_stat st; + if (zip_source_stat(src, &st) != 0) { + zip_error_set_from_source(&ctx->error, src); + return -1; + } + set_mtime(ctx, &st); } - _zip_u2d_time(st.mtime, &dostime, &dosdate); + _zip_u2d_time(ctx->mtime, &dostime, &dosdate); if ((ctx->buffer = _zip_buffer_new(NULL, ZIP_CRYPTO_PKWARE_HEADERLEN)) == NULL) { zip_error_set(&ctx->error, ZIP_ER_MEMORY, 0); @@ -156,7 +161,7 @@ pkware_encrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t length, zip } if ((n = zip_source_read(src, data, length)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -182,6 +187,9 @@ pkware_encrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t length, zip if (st->valid & ZIP_STAT_COMP_SIZE) { st->comp_size += ZIP_CRYPTO_PKWARE_HEADERLEN; } + set_mtime(ctx, st); + st->mtime = ctx->mtime; + st->valid |= ZIP_STAT_MTIME; return 0; } @@ -209,8 +217,7 @@ pkware_encrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t length, zip return 0; default: - zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); - return -1; + return zip_source_pass_to_lower_layer(src, data, length, cmd); } } @@ -230,6 +237,8 @@ trad_pkware_new(const char *password, zip_error_t *error) { return NULL; } ctx->buffer = NULL; + ctx->mtime_set = false; + ctx->mtime = 0; zip_error_init(&ctx->error); return ctx; @@ -247,3 +256,16 @@ trad_pkware_free(struct trad_pkware *ctx) { zip_error_fini(&ctx->error); free(ctx); } + + +static void set_mtime(struct trad_pkware* ctx, zip_stat_t* st) { + if (!ctx->mtime_set) { + if (st->valid & ZIP_STAT_MTIME) { + ctx->mtime = st->mtime; + } + else { + time(&ctx->mtime); + } + ctx->mtime_set = true; + } +} diff --git a/core/deps/libzip/lib/zip_source_read.c b/core/deps/libzip/lib/zip_source_read.c index f146ab065..0938fcb08 100644 --- a/core/deps/libzip/lib/zip_source_read.c +++ b/core/deps/libzip/lib/zip_source_read.c @@ -1,9 +1,9 @@ /* zip_source_read.c -- read data from zip_source - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_remove.c b/core/deps/libzip/lib/zip_source_remove.c index a6ccd7eda..c1d73ab92 100644 --- a/core/deps/libzip/lib/zip_source_remove.c +++ b/core/deps/libzip/lib/zip_source_remove.c @@ -1,9 +1,9 @@ /* zip_source_remove.c -- remove empty archive - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,6 +37,11 @@ int zip_source_remove(zip_source_t *src) { + if (ZIP_SOURCE_IS_LAYERED(src)) { + zip_error_set(&src->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } + if (src->write_state == ZIP_SOURCE_WRITE_REMOVED) { return 0; } diff --git a/core/deps/libzip/lib/zip_source_rollback_write.c b/core/deps/libzip/lib/zip_source_rollback_write.c index d21b1ffe2..ea1a15107 100644 --- a/core/deps/libzip/lib/zip_source_rollback_write.c +++ b/core/deps/libzip/lib/zip_source_rollback_write.c @@ -1,9 +1,9 @@ /* zip_source_rollback_write.c -- discard changes - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,6 +37,10 @@ ZIP_EXTERN void zip_source_rollback_write(zip_source_t *src) { + if (ZIP_SOURCE_IS_LAYERED(src)) { + return; + } + if (src->write_state != ZIP_SOURCE_WRITE_OPEN && src->write_state != ZIP_SOURCE_WRITE_FAILED) { return; } diff --git a/core/deps/libzip/lib/zip_source_seek.c b/core/deps/libzip/lib/zip_source_seek.c index 29d1e968e..e3baad5ab 100644 --- a/core/deps/libzip/lib/zip_source_seek.c +++ b/core/deps/libzip/lib/zip_source_seek.c @@ -1,9 +1,9 @@ /* zip_source_seek.c -- seek to offset - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_seek_write.c b/core/deps/libzip/lib/zip_source_seek_write.c index 5fa2a8129..34ae2f5af 100644 --- a/core/deps/libzip/lib/zip_source_seek_write.c +++ b/core/deps/libzip/lib/zip_source_seek_write.c @@ -1,9 +1,9 @@ /* zip_source_seek_write.c -- seek to offset for writing - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -39,6 +39,11 @@ ZIP_EXTERN int zip_source_seek_write(zip_source_t *src, zip_int64_t offset, int whence) { zip_source_args_seek_t args; + if (ZIP_SOURCE_IS_LAYERED(src)) { + zip_error_set(&src->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } + if (!ZIP_SOURCE_IS_OPEN_WRITING(src) || (whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END)) { zip_error_set(&src->error, ZIP_ER_INVAL, 0); return -1; diff --git a/core/deps/libzip/lib/zip_source_stat.c b/core/deps/libzip/lib/zip_source_stat.c index 46eb92db5..05dcb84d7 100644 --- a/core/deps/libzip/lib/zip_source_stat.c +++ b/core/deps/libzip/lib/zip_source_stat.c @@ -1,9 +1,9 @@ /* zip_source_stat.c -- get meta information from zip_source - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -45,11 +45,15 @@ zip_source_stat(zip_source_t *src, zip_stat_t *st) { return -1; } + if (src->write_state == ZIP_SOURCE_WRITE_REMOVED) { + zip_error_set(&src->error, ZIP_ER_READ, ENOENT); + } + zip_stat_init(st); if (ZIP_SOURCE_IS_LAYERED(src)) { if (zip_source_stat(src->src, st) < 0) { - _zip_error_set_from_source(&src->error, src->src); + zip_error_set_from_source(&src->error, src->src); return -1; } } diff --git a/core/deps/libzip/lib/zip_source_supports.c b/core/deps/libzip/lib/zip_source_supports.c index b4575a74b..8fea2ae91 100644 --- a/core/deps/libzip/lib/zip_source_supports.c +++ b/core/deps/libzip/lib/zip_source_supports.c @@ -1,9 +1,9 @@ /* zip_source_supports.c -- check for supported functions - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -42,6 +42,10 @@ zip_source_supports(zip_source_t *src) { return src->supports; } +bool +zip_source_supports_reopen(zip_source_t *src) { + return (zip_source_supports(src) & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_SUPPORTS_REOPEN)) != 0; +} ZIP_EXTERN zip_int64_t zip_source_make_command_bitmap(zip_source_cmd_t cmd0, ...) { @@ -63,3 +67,8 @@ zip_source_make_command_bitmap(zip_source_cmd_t cmd0, ...) { return bitmap; } + + +ZIP_EXTERN int zip_source_is_seekable(zip_source_t *src) { + return ZIP_SOURCE_CHECK_SUPPORTED(zip_source_supports(src->src), ZIP_SOURCE_SEEK); +} diff --git a/core/deps/libzip/lib/zip_source_tell.c b/core/deps/libzip/lib/zip_source_tell.c index b17412be7..49057ce56 100644 --- a/core/deps/libzip/lib/zip_source_tell.c +++ b/core/deps/libzip/lib/zip_source_tell.c @@ -1,9 +1,9 @@ /* zip_source_tell.c -- report current offset - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_tell_write.c b/core/deps/libzip/lib/zip_source_tell_write.c index c09a79fcb..a5b0e5311 100644 --- a/core/deps/libzip/lib/zip_source_tell_write.c +++ b/core/deps/libzip/lib/zip_source_tell_write.c @@ -1,9 +1,9 @@ /* zip_source_tell_write.c -- report current offset for writing - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -37,6 +37,11 @@ ZIP_EXTERN zip_int64_t zip_source_tell_write(zip_source_t *src) { + if (ZIP_SOURCE_IS_LAYERED(src)) { + zip_error_set(&src->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } + if (!ZIP_SOURCE_IS_OPEN_WRITING(src)) { zip_error_set(&src->error, ZIP_ER_INVAL, 0); return -1; diff --git a/core/deps/libzip/lib/zip_source_window.c b/core/deps/libzip/lib/zip_source_window.c index b63ba1df4..524e27c83 100644 --- a/core/deps/libzip/lib/zip_source_window.c +++ b/core/deps/libzip/lib/zip_source_window.c @@ -1,9 +1,9 @@ /* zip_source_window.c -- return part of lower source - Copyright (C) 2012-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2012-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -40,6 +40,7 @@ struct window { zip_uint64_t start; /* where in file we start reading */ zip_uint64_t end; /* where in file we stop reading */ + bool end_valid; /* whether end is set, otherwise read until EOF */ /* if not NULL, read file data for this file */ zip_t *source_archive; @@ -48,6 +49,7 @@ struct window { zip_uint64_t offset; /* offset in src for next read */ zip_stat_t stat; + zip_uint64_t stat_invalid; zip_file_attributes_t attributes; zip_error_t error; zip_int64_t supports; @@ -57,20 +59,28 @@ struct window { static zip_int64_t window_read(zip_source_t *, void *, void *, zip_uint64_t, zip_source_cmd_t); -zip_source_t * -zip_source_window(zip_t *za, zip_source_t *src, zip_uint64_t start, zip_uint64_t len) { - return _zip_source_window_new(src, start, len, NULL, 0, NULL, 0, &za->error); +ZIP_EXTERN zip_source_t * +zip_source_window_create(zip_source_t *src, zip_uint64_t start, zip_int64_t len, zip_error_t *error) { + return _zip_source_window_new(src, start, len, NULL, 0, NULL, NULL, 0, false, error); } zip_source_t * -_zip_source_window_new(zip_source_t *src, zip_uint64_t start, zip_uint64_t length, zip_stat_t *st, zip_file_attributes_t *attributes, zip_t *source_archive, zip_uint64_t source_index, zip_error_t *error) { +_zip_source_window_new(zip_source_t *src, zip_uint64_t start, zip_int64_t length, zip_stat_t *st, zip_uint64_t st_invalid, zip_file_attributes_t *attributes, zip_t *source_archive, zip_uint64_t source_index, bool take_ownership, zip_error_t *error) { + zip_source_t* window_source; struct window *ctx; - if (src == NULL || start + length < start || (source_archive == NULL && source_index != 0)) { + if (src == NULL || length < -1 || (source_archive == NULL && source_index != 0)) { zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } + + if (length >= 0) { + if (start + (zip_uint64_t)length < start) { + zip_error_set(error, ZIP_ER_INVAL, 0); + return NULL; + } + } if ((ctx = (struct window *)malloc(sizeof(*ctx))) == NULL) { zip_error_set(error, ZIP_ER_MEMORY, 0); @@ -78,10 +88,17 @@ _zip_source_window_new(zip_source_t *src, zip_uint64_t start, zip_uint64_t lengt } ctx->start = start; - ctx->end = start + length; + if (length == -1) { + ctx->end_valid = false; + } + else { + ctx->end = start + (zip_uint64_t)length; + ctx->end_valid = true; + } zip_stat_init(&ctx->stat); + ctx->stat_invalid = st_invalid; if (attributes != NULL) { - memcpy(&ctx->attributes, attributes, sizeof(ctx->attributes)); + (void)memcpy_s(&ctx->attributes, sizeof(ctx->attributes), attributes, sizeof(ctx->attributes)); } else { zip_file_attributes_init(&ctx->attributes); @@ -89,7 +106,7 @@ _zip_source_window_new(zip_source_t *src, zip_uint64_t start, zip_uint64_t lengt ctx->source_archive = source_archive; ctx->source_index = source_index; zip_error_init(&ctx->error); - ctx->supports = (zip_source_supports(src) & ZIP_SOURCE_SUPPORTS_SEEKABLE) | (zip_source_make_command_bitmap(ZIP_SOURCE_GET_FILE_ATTRIBUTES, ZIP_SOURCE_SUPPORTS, ZIP_SOURCE_TELL, -1)); + ctx->supports = (zip_source_supports(src) & (ZIP_SOURCE_SUPPORTS_SEEKABLE | ZIP_SOURCE_SUPPORTS_REOPEN)) | (zip_source_make_command_bitmap(ZIP_SOURCE_GET_FILE_ATTRIBUTES, ZIP_SOURCE_SUPPORTS, ZIP_SOURCE_TELL, ZIP_SOURCE_FREE, -1)); ctx->needs_seek = (ctx->supports & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_SEEK)) ? true : false; if (st) { @@ -98,8 +115,12 @@ _zip_source_window_new(zip_source_t *src, zip_uint64_t start, zip_uint64_t lengt return NULL; } } - - return zip_source_layered_create(src, window_read, ctx, error); + + window_source = zip_source_layered_create(src, window_read, ctx, error); + if (window_source != NULL && !take_ownership) { + zip_source_keep(src); + } + return window_source; } @@ -149,7 +170,7 @@ window_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_sou } if (ctx->end + offset < ctx->end) { /* zip archive data claims end of data past zip64 limits */ - zip_error_set(&ctx->error, ZIP_ER_INCONS, 0); + zip_error_set(&ctx->error, ZIP_ER_INCONS, MAKE_DETAIL_WITH_INDEX(ZIP_ER_DETAIL_CDIR_ENTRY_INVALID, ctx->source_index)); return -1; } ctx->start += offset; @@ -168,7 +189,7 @@ window_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_sou for (n = 0; n < ctx->start; n += (zip_uint64_t)ret) { i = (ctx->start - n > BUFSIZE ? BUFSIZE : ctx->start - n); if ((ret = zip_source_read(src, b, i)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); byte_array_fini(b); return -1; } @@ -186,15 +207,17 @@ window_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_sou return 0; case ZIP_SOURCE_READ: - if (len > ctx->end - ctx->offset) + if (ctx->end_valid && len > ctx->end - ctx->offset) { len = ctx->end - ctx->offset; + } - if (len == 0) + if (len == 0) { return 0; + } if (ctx->needs_seek) { if (zip_source_seek(src, (zip_int64_t)ctx->offset, SEEK_SET) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } } @@ -207,7 +230,7 @@ window_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_sou ctx->offset += (zip_uint64_t)ret; if (ret == 0) { - if (ctx->offset < ctx->end) { + if (ctx->end_valid && ctx->offset < ctx->end) { zip_error_set(&ctx->error, ZIP_ER_EOF, 0); return -1; } @@ -215,12 +238,40 @@ window_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_sou return ret; case ZIP_SOURCE_SEEK: { - zip_int64_t new_offset = zip_source_seek_compute_offset(ctx->offset - ctx->start, ctx->end - ctx->start, data, len, &ctx->error); + zip_int64_t new_offset; + + if (!ctx->end_valid) { + zip_source_args_seek_t *args = ZIP_SOURCE_GET_ARGS(zip_source_args_seek_t, data, len, &ctx->error); + + if (args == NULL) { + return -1; + } + if (args->whence == SEEK_END) { + if (zip_source_seek(src, args->offset, args->whence) < 0) { + zip_error_set_from_source(&ctx->error, src); + return -1; + } + new_offset = zip_source_tell(src); + if (new_offset < 0) { + zip_error_set_from_source(&ctx->error, src); + return -1; + } + if ((zip_uint64_t)new_offset < ctx->start) { + zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); + (void)zip_source_seek(src, (zip_int64_t)ctx->offset, SEEK_SET); + return -1; + } + ctx->offset = (zip_uint64_t)new_offset; + return 0; + } + } + new_offset = zip_source_seek_compute_offset(ctx->offset - ctx->start, ctx->end - ctx->start, data, len, &ctx->error); + if (new_offset < 0) { return -1; } - + ctx->offset = (zip_uint64_t)new_offset + ctx->start; return 0; } @@ -233,6 +284,19 @@ window_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_sou if (_zip_stat_merge(st, &ctx->stat, &ctx->error) < 0) { return -1; } + + if (!(ctx->stat.valid & ZIP_STAT_SIZE)) { + if (ctx->end_valid) { + st->valid |= ZIP_STAT_SIZE; + st->size = ctx->end - ctx->start; + } + else if (st->valid & ZIP_STAT_SIZE) { + st->size -= ctx->start; + } + } + + st->valid &= ~ctx->stat_invalid; + return 0; } @@ -242,7 +306,7 @@ window_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_sou return -1; } - memcpy(data, &ctx->attributes, sizeof(ctx->attributes)); + (void)memcpy_s(data, sizeof(ctx->attributes), &ctx->attributes, sizeof(ctx->attributes)); return sizeof(ctx->attributes); case ZIP_SOURCE_SUPPORTS: @@ -252,8 +316,7 @@ window_read(zip_source_t *src, void *_ctx, void *data, zip_uint64_t len, zip_sou return (zip_int64_t)(ctx->offset - ctx->start); default: - zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0); - return -1; + return zip_source_pass_to_lower_layer(src, data, len, cmd); } } diff --git a/core/deps/libzip/lib/zip_source_winzip_aes_decode.c b/core/deps/libzip/lib/zip_source_winzip_aes_decode.c index b2bb3cbb7..ee53fb419 100644 --- a/core/deps/libzip/lib/zip_source_winzip_aes_decode.c +++ b/core/deps/libzip/lib/zip_source_winzip_aes_decode.c @@ -1,9 +1,9 @@ /* zip_source_winzip_aes_decode.c -- Winzip AES decryption routines - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -36,6 +36,7 @@ #include #include "zipint.h" +#include "zip_crypto.h" struct winzip_aes { char *password; @@ -72,7 +73,7 @@ zip_source_winzip_aes_decode(zip_t *za, zip_source_t *src, zip_uint16_t encrypti } if (zip_source_stat(src, &st) != 0) { - _zip_error_set_from_source(&za->error, src); + zip_error_set_from_source(&za->error, src); return NULL; } @@ -107,7 +108,7 @@ decrypt_header(zip_source_t *src, struct winzip_aes *ctx) { headerlen = WINZIP_AES_PASSWORD_VERIFY_LENGTH + SALT_LENGTH(ctx->encryption_method); if ((n = zip_source_read(src, header, headerlen)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -131,9 +132,9 @@ decrypt_header(zip_source_t *src, struct winzip_aes *ctx) { static bool verify_hmac(zip_source_t *src, struct winzip_aes *ctx) { - unsigned char computed[SHA1_LENGTH], from_file[HMAC_LENGTH]; + unsigned char computed[ZIP_CRYPTO_SHA1_LENGTH], from_file[HMAC_LENGTH]; if (zip_source_read(src, from_file, HMAC_LENGTH) < HMAC_LENGTH) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return false; } @@ -181,7 +182,7 @@ winzip_aes_decrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zi } if ((n = zip_source_read(src, data, len)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } ctx->current_position += (zip_uint64_t)n; @@ -211,7 +212,7 @@ winzip_aes_decrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zi } case ZIP_SOURCE_SUPPORTS: - return zip_source_make_command_bitmap(ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_CLOSE, ZIP_SOURCE_STAT, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, -1); + return zip_source_make_command_bitmap(ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_CLOSE, ZIP_SOURCE_STAT, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, ZIP_SOURCE_SUPPORTS_REOPEN, -1); case ZIP_SOURCE_ERROR: return zip_error_to_data(&ctx->error, data, len); @@ -221,8 +222,7 @@ winzip_aes_decrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zi return 0; default: - zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); - return -1; + return zip_source_pass_to_lower_layer(src, data, len, cmd); } } diff --git a/core/deps/libzip/lib/zip_source_winzip_aes_encode.c b/core/deps/libzip/lib/zip_source_winzip_aes_encode.c index 19d5cf026..87e2865a0 100644 --- a/core/deps/libzip/lib/zip_source_winzip_aes_encode.c +++ b/core/deps/libzip/lib/zip_source_winzip_aes_encode.c @@ -1,9 +1,9 @@ /* zip_source_winzip_aes_encode.c -- Winzip AES encryption routines - Copyright (C) 2009-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2009-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -36,13 +36,13 @@ #include #include "zipint.h" - +#include "zip_crypto.h" struct winzip_aes { char *password; zip_uint16_t encryption_method; - zip_uint8_t data[ZIP_MAX(WINZIP_AES_MAX_HEADER_LENGTH, SHA1_LENGTH)]; + zip_uint8_t data[ZIP_MAX(WINZIP_AES_MAX_HEADER_LENGTH, ZIP_CRYPTO_SHA1_LENGTH)]; zip_buffer_t *buffer; zip_winzip_aes_t *aes_ctx; @@ -139,7 +139,7 @@ winzip_aes_encrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t length, } if ((ret = zip_source_read(src, data, length)) < 0) { - _zip_error_set_from_source(&ctx->error, src); + zip_error_set_from_source(&ctx->error, src); return -1; } @@ -207,8 +207,7 @@ winzip_aes_encrypt(zip_source_t *src, void *ud, void *data, zip_uint64_t length, return 0; default: - zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); - return -1; + return zip_source_pass_to_lower_layer(src, data, length, cmd); } } diff --git a/core/deps/libzip/lib/zip_source_write.c b/core/deps/libzip/lib/zip_source_write.c index 33bf9c944..24dde9b24 100644 --- a/core/deps/libzip/lib/zip_source_write.c +++ b/core/deps/libzip/lib/zip_source_write.c @@ -1,9 +1,9 @@ /* zip_source_write.c -- start a new file for writing - Copyright (C) 2014-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_source_zip.c b/core/deps/libzip/lib/zip_source_zip.c index cb62540d5..faabf0d29 100644 --- a/core/deps/libzip/lib/zip_source_zip.c +++ b/core/deps/libzip/lib/zip_source_zip.c @@ -1,9 +1,9 @@ /* zip_source_zip.c -- create data source from zip file - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -34,23 +34,30 @@ #include +#define _ZIP_COMPILING_DEPRECATED #include "zipint.h" - -ZIP_EXTERN zip_source_t * -zip_source_zip(zip_t *za, zip_t *srcza, zip_uint64_t srcidx, zip_flags_t flags, zip_uint64_t start, zip_int64_t len) { +ZIP_EXTERN zip_source_t *zip_source_zip_create(zip_t *srcza, zip_uint64_t srcidx, zip_flags_t flags, zip_uint64_t start, zip_int64_t len, zip_error_t *error) { if (len < -1) { - zip_error_set(&za->error, ZIP_ER_INVAL, 0); + zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } - - if (len == -1) - len = 0; - - if (start == 0 && len == 0) + + if (len == 0) { + len = -1; + } + + if (start == 0 && len == -1) { flags |= ZIP_FL_COMPRESSED; - else + } + else { flags &= ~ZIP_FL_COMPRESSED; + } - return _zip_source_zip_new(za, srcza, srcidx, flags, start, (zip_uint64_t)len, NULL); + return zip_source_zip_file_create(srcza, srcidx, flags, start, len, NULL, error); +} + + +ZIP_EXTERN zip_source_t *zip_source_zip(zip_t *za, zip_t *srcza, zip_uint64_t srcidx, zip_flags_t flags, zip_uint64_t start, zip_int64_t len) { + return zip_source_zip_create(srcza, srcidx, flags, start, len, &za->error); } diff --git a/core/deps/libzip/lib/zip_source_zip_new.c b/core/deps/libzip/lib/zip_source_zip_new.c index ffb2bdf4b..ecccdd68f 100644 --- a/core/deps/libzip/lib/zip_source_zip_new.c +++ b/core/deps/libzip/lib/zip_source_zip_new.c @@ -1,9 +1,9 @@ /* zip_source_zip_new.c -- prepare data structures for zip_fopen/zip_source_zip - Copyright (C) 2012-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2012-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -38,30 +38,25 @@ static void _zip_file_attributes_from_dirent(zip_file_attributes_t *attributes, zip_dirent_t *de); -zip_source_t * -_zip_source_zip_new(zip_t *za, zip_t *srcza, zip_uint64_t srcidx, zip_flags_t flags, zip_uint64_t start, zip_uint64_t len, const char *password) { +ZIP_EXTERN zip_source_t *zip_source_zip_file(zip_t* za, zip_t *srcza, zip_uint64_t srcidx, zip_flags_t flags, zip_uint64_t start, zip_int64_t len, const char *password) { + return zip_source_zip_file_create(srcza, srcidx, flags, start, len, password, &za->error); +} + + +ZIP_EXTERN zip_source_t *zip_source_zip_file_create(zip_t *srcza, zip_uint64_t srcidx, zip_flags_t flags, zip_uint64_t start, zip_int64_t len, const char *password, zip_error_t *error) { + /* TODO: We need to make sure that the returned source is invalidated when srcza is closed. */ zip_source_t *src, *s2; zip_stat_t st; zip_file_attributes_t attributes; zip_dirent_t *de; - bool partial_data, needs_crc, needs_decrypt, needs_decompress; + bool partial_data, needs_crc, encrypted, needs_decrypt, compressed, needs_decompress, changed_data, have_size, have_comp_size; + zip_flags_t stat_flags; + zip_int64_t data_len; + bool take_ownership = false; + bool empty_data = false; - if (za == NULL) { - return NULL; - } - - if (srcza == NULL || srcidx >= srcza->nentry) { - zip_error_set(&za->error, ZIP_ER_INVAL, 0); - return NULL; - } - - if ((flags & ZIP_FL_UNCHANGED) == 0 && (ZIP_ENTRY_DATA_CHANGED(srcza->entry + srcidx) || srcza->entry[srcidx].deleted)) { - zip_error_set(&za->error, ZIP_ER_CHANGED, 0); - return NULL; - } - - if (zip_stat_index(srcza, srcidx, flags | ZIP_FL_UNCHANGED, &st) < 0) { - zip_error_set(&za->error, ZIP_ER_INTERNAL, 0); + if (srcza == NULL || srcidx >= srcza->nentry || len < -1) { + zip_error_set(error, ZIP_ER_INVAL, 0); return NULL; } @@ -69,65 +64,184 @@ _zip_source_zip_new(zip_t *za, zip_t *srcza, zip_uint64_t srcidx, zip_flags_t fl flags |= ZIP_FL_COMPRESSED; } - if ((start > 0 || len > 0) && (flags & ZIP_FL_COMPRESSED)) { - zip_error_set(&za->error, ZIP_ER_INVAL, 0); - return NULL; - } + changed_data = false; + if ((flags & ZIP_FL_UNCHANGED) == 0) { + zip_entry_t *entry = srcza->entry + srcidx; + if (ZIP_ENTRY_DATA_CHANGED(entry)) { + if ((flags & ZIP_FL_COMPRESSED) || !zip_source_supports_reopen(entry->source)) { + zip_error_set(error, ZIP_ER_CHANGED, 0); + return NULL; + } - /* overflow or past end of file */ - if ((start > 0 || len > 0) && (start + len < start || start + len > st.size)) { - zip_error_set(&za->error, ZIP_ER_INVAL, 0); - return NULL; - } - - if (len == 0) { - len = st.size - start; - } - - partial_data = len < st.size; - needs_decrypt = ((flags & ZIP_FL_ENCRYPTED) == 0) && (st.encryption_method != ZIP_EM_NONE); - needs_decompress = ((flags & ZIP_FL_COMPRESSED) == 0) && (st.comp_method != ZIP_CM_STORE); - /* when reading the whole file, check for CRC errors */ - needs_crc = ((flags & ZIP_FL_COMPRESSED) == 0 || st.comp_method == ZIP_CM_STORE) && !partial_data; - - if (needs_decrypt) { - if (password == NULL) { - password = za->default_password; + changed_data = true; } - if (password == NULL) { - zip_error_set(&za->error, ZIP_ER_NOPASSWD, 0); + else if (entry->deleted) { + zip_error_set(error, ZIP_ER_CHANGED, 0); return NULL; } } - if ((de = _zip_get_dirent(srcza, srcidx, flags, &za->error)) == NULL) { + stat_flags = flags; + if (!changed_data) { + stat_flags |= ZIP_FL_UNCHANGED; + } + + if (zip_stat_index(srcza, srcidx, stat_flags, &st) < 0) { + zip_error_set(error, ZIP_ER_INTERNAL, 0); + return NULL; + } + + if ((start > 0 || len >= 0) && (flags & ZIP_FL_COMPRESSED)) { + zip_error_set(error, ZIP_ER_INVAL, 0); + return NULL; + } + + have_size = (st.valid & ZIP_STAT_SIZE) != 0; + /* overflow or past end of file */ + if (len >= 0 && ((start > 0 && start + len < start) || (have_size && start + len > st.size))) { + zip_error_set(error, ZIP_ER_INVAL, 0); + return NULL; + } + + if (len == -1) { + if (have_size) { + if (st.size - start > ZIP_INT64_MAX) { + zip_error_set(error, ZIP_ER_INVAL, 0); + return NULL; + } + data_len = (zip_int64_t)(st.size - start); + } + else { + data_len = -1; + } + } + else { + data_len = len; + } + + if (have_size) { + partial_data = (zip_uint64_t)(data_len) < st.size; + } + else { + partial_data = true; + } + encrypted = (st.valid & ZIP_STAT_ENCRYPTION_METHOD) && (st.encryption_method != ZIP_EM_NONE); + needs_decrypt = ((flags & ZIP_FL_ENCRYPTED) == 0) && encrypted; + compressed = (st.valid & ZIP_STAT_COMP_METHOD) && (st.comp_method != ZIP_CM_STORE); + needs_decompress = ((flags & ZIP_FL_COMPRESSED) == 0) && compressed; + /* when reading the whole file, check for CRC errors */ + needs_crc = ((flags & ZIP_FL_COMPRESSED) == 0 || !compressed) && !partial_data && (st.valid & ZIP_STAT_CRC) != 0; + + if (needs_decrypt) { + if (password == NULL) { + password = srcza->default_password; + } + if (password == NULL) { + zip_error_set(error, ZIP_ER_NOPASSWD, 0); + return NULL; + } + } + + if ((de = _zip_get_dirent(srcza, srcidx, flags, error)) == NULL) { return NULL; } _zip_file_attributes_from_dirent(&attributes, de); - if (st.comp_size == 0) { - return zip_source_buffer_with_attributes(za, NULL, 0, 0, &attributes); + have_comp_size = (st.valid & ZIP_STAT_COMP_SIZE) != 0; + if (needs_decrypt || needs_decompress) { + empty_data = (have_comp_size && st.comp_size == 0); } + else { + empty_data = (have_size && st.size == 0); + } + if (empty_data) { + src = zip_source_buffer_with_attributes_create(NULL, 0, 0, &attributes, error); + } + else { + src = NULL; + } + + + /* If we created source buffer above, we want the window source to take ownership of it. */ + take_ownership = src != NULL; + /* if we created a buffer source above, then treat it as if + reading the changed data - that way we don't need add another + special case to the code below that wraps it in the window + source */ + changed_data = changed_data || (src != NULL); if (partial_data && !needs_decrypt && !needs_decompress) { struct zip_stat st2; + zip_t *source_archive; + zip_uint64_t source_index; + + if (changed_data) { + if (src == NULL) { + src = srcza->entry[srcidx].source; + } + source_archive = NULL; + source_index = 0; + } + else { + src = srcza->src; + source_archive = srcza; + source_index = srcidx; + } - st2.size = len; - st2.comp_size = len; st2.comp_method = ZIP_CM_STORE; - st2.mtime = st.mtime; - st2.valid = ZIP_STAT_SIZE | ZIP_STAT_COMP_SIZE | ZIP_STAT_COMP_METHOD | ZIP_STAT_MTIME; + st2.valid = ZIP_STAT_COMP_METHOD; + if (data_len >= 0) { + st2.size = (zip_uint64_t)data_len; + st2.comp_size = (zip_uint64_t)data_len; + st2.valid |= ZIP_STAT_SIZE | ZIP_STAT_COMP_SIZE; + } + if (st.valid & ZIP_STAT_MTIME) { + st2.mtime = st.mtime; + st2.valid |= ZIP_STAT_MTIME; + } - if ((src = _zip_source_window_new(srcza->src, start, len, &st2, &attributes, srcza, srcidx, &za->error)) == NULL) { + if ((src = _zip_source_window_new(src, start, data_len, &st2, ZIP_STAT_NAME, &attributes, source_archive, source_index, take_ownership, error)) == NULL) { + return NULL; + } + } + /* here we restrict src to file data, so no point in doing it for + source that already represents only the file data */ + else if (!changed_data) { + /* this branch is executed only for archive sources; we know + that stat data come from the archive too, so it's safe to + assume that st has a comp_size specified */ + if (st.comp_size > ZIP_INT64_MAX) { + zip_error_set(error, ZIP_ER_INVAL, 0); + return NULL; + } + /* despite the fact that we want the whole data file, we still + wrap the source into a window source to add st and + attributes and to have a source that positions the read + offset properly before each read for multiple zip_file_t + referring to the same underlying source */ + if ((src = _zip_source_window_new(srcza->src, 0, (zip_int64_t)st.comp_size, &st, ZIP_STAT_NAME, &attributes, srcza, srcidx, take_ownership, error)) == NULL) { return NULL; } } else { - if ((src = _zip_source_window_new(srcza->src, 0, st.comp_size, &st, &attributes, srcza, srcidx, &za->error)) == NULL) { + /* this branch gets executed when reading the whole changed + data file or when "reading" from a zero-sized source buffer + that we created above */ + if (src == NULL) { + src = srcza->entry[srcidx].source; + } + /* despite the fact that we want the whole data file, we still + wrap the source into a window source to add st and + attributes and to have a source that positions the read + offset properly before each read for multiple zip_file_t + referring to the same underlying source */ + if ((src = _zip_source_window_new(src, 0, data_len, &st, ZIP_STAT_NAME, &attributes, NULL, 0, take_ownership, error)) == NULL) { return NULL; } } + /* In all cases, src is a window source and therefore is owned by this function. */ + if (_zip_source_set_source_archive(src, srcza) < 0) { zip_source_free(src); return NULL; @@ -139,38 +253,45 @@ _zip_source_zip_new(zip_t *za, zip_t *srcza, zip_uint64_t srcidx, zip_flags_t fl zip_encryption_implementation enc_impl; if ((enc_impl = _zip_get_encryption_implementation(st.encryption_method, ZIP_CODEC_DECODE)) == NULL) { - zip_error_set(&za->error, ZIP_ER_ENCRNOTSUPP, 0); + zip_error_set(error, ZIP_ER_ENCRNOTSUPP, 0); return NULL; } - s2 = enc_impl(za, src, st.encryption_method, 0, password); - zip_source_free(src); + s2 = enc_impl(srcza, src, st.encryption_method, 0, password); if (s2 == NULL) { + zip_source_free(src); return NULL; } + src = s2; } if (needs_decompress) { - s2 = zip_source_decompress(za, src, st.comp_method); - zip_source_free(src); + s2 = zip_source_decompress(srcza, src, st.comp_method); if (s2 == NULL) { + zip_source_free(src); return NULL; } src = s2; } if (needs_crc) { - s2 = zip_source_crc(za, src, 1); - zip_source_free(src); + s2 = zip_source_crc_create(src, 1, error); if (s2 == NULL) { + zip_source_free(src); return NULL; } src = s2; } if (partial_data && (needs_decrypt || needs_decompress)) { - s2 = zip_source_window(za, src, start, len); - zip_source_free(src); + zip_stat_t st2; + zip_stat_init(&st2); + if (data_len >= 0) { + st2.valid = ZIP_STAT_SIZE; + st2.size = (zip_uint64_t)data_len; + } + s2 = _zip_source_window_new(src, start, data_len, &st2, ZIP_STAT_NAME, NULL, NULL, 0, true, error); if (s2 == NULL) { + zip_source_free(src); return NULL; } src = s2; diff --git a/core/deps/libzip/lib/zip_stat.c b/core/deps/libzip/lib/zip_stat.c index c328dcfb4..51d8026dd 100644 --- a/core/deps/libzip/lib/zip_stat.c +++ b/core/deps/libzip/lib/zip_stat.c @@ -1,9 +1,9 @@ /* zip_stat.c -- get information about file by name - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_stat_index.c b/core/deps/libzip/lib/zip_stat_index.c index 62dc0455e..da33c09eb 100644 --- a/core/deps/libzip/lib/zip_stat_index.c +++ b/core/deps/libzip/lib/zip_stat_index.c @@ -3,7 +3,7 @@ Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -39,22 +39,43 @@ ZIP_EXTERN int zip_stat_index(zip_t *za, zip_uint64_t index, zip_flags_t flags, zip_stat_t *st) { const char *name; zip_dirent_t *de; + zip_entry_t *entry; - if ((de = _zip_get_dirent(za, index, flags, NULL)) == NULL) + if ((de = _zip_get_dirent(za, index, flags, NULL)) == NULL) { return -1; + } - if ((name = zip_get_name(za, index, flags)) == NULL) + if ((name = zip_get_name(za, index, flags)) == NULL) { return -1; + } + entry = za->entry + index; if ((flags & ZIP_FL_UNCHANGED) == 0 && ZIP_ENTRY_DATA_CHANGED(za->entry + index)) { - zip_entry_t *entry = za->entry + index; if (zip_source_stat(entry->source, st) < 0) { zip_error_set(&za->error, ZIP_ER_CHANGED, 0); return -1; } + if (ZIP_CM_IS_DEFAULT(de->comp_method)) { + if (!(st->valid & ZIP_STAT_COMP_METHOD) || st->comp_method == ZIP_CM_STORE) { + st->valid &= ~(ZIP_STAT_COMP_SIZE|ZIP_STAT_COMP_METHOD); + } + } + else { + if ((st->valid & ZIP_STAT_COMP_METHOD) && st->comp_method != de->comp_method) { + st->valid &= ~ZIP_STAT_COMP_SIZE; + } + st->valid |= ZIP_STAT_COMP_METHOD; + st->comp_method = de->comp_method; + } + + if (((st->valid & (ZIP_STAT_COMP_METHOD|ZIP_STAT_SIZE)) == (ZIP_STAT_COMP_METHOD|ZIP_STAT_SIZE)) && st->comp_method == ZIP_CM_STORE) { + st->valid |= ZIP_STAT_COMP_SIZE; + st->comp_size = st->size; + } + if (entry->changes != NULL && entry->changes->changed & ZIP_DIRENT_LAST_MOD) { st->mtime = de->last_mod; st->valid |= ZIP_STAT_MTIME; @@ -70,6 +91,16 @@ zip_stat_index(zip_t *za, zip_uint64_t index, zip_flags_t flags, zip_stat_t *st) st->comp_method = (zip_uint16_t)de->comp_method; st->encryption_method = de->encryption_method; st->valid = (de->crc_valid ? ZIP_STAT_CRC : 0) | ZIP_STAT_SIZE | ZIP_STAT_MTIME | ZIP_STAT_COMP_SIZE | ZIP_STAT_COMP_METHOD | ZIP_STAT_ENCRYPTION_METHOD; + if (entry->changes != NULL && entry->changes->changed & ZIP_DIRENT_COMP_METHOD) { + st->valid &= ~ZIP_STAT_COMP_SIZE; + } + } + + if ((za->ch_flags & ZIP_AFL_WANT_TORRENTZIP) && (flags & ZIP_FL_UNCHANGED) == 0) { + st->comp_method = ZIP_CM_DEFLATE; + st->mtime = _zip_d2u_time(0xbc00, 0x2198); + st->valid |= ZIP_STAT_MTIME | ZIP_STAT_COMP_METHOD; + st->valid &= ~ZIP_STAT_COMP_SIZE; } st->index = index; diff --git a/core/deps/libzip/lib/zip_stat_init.c b/core/deps/libzip/lib/zip_stat_init.c index 0cf51efad..9c6088a79 100644 --- a/core/deps/libzip/lib/zip_stat_init.c +++ b/core/deps/libzip/lib/zip_stat_init.c @@ -1,9 +1,9 @@ /* zip_stat_init.c -- initialize struct zip_stat. - Copyright (C) 2006-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2006-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_strerror.c b/core/deps/libzip/lib/zip_strerror.c index 6adec16cd..7d8279318 100644 --- a/core/deps/libzip/lib/zip_strerror.c +++ b/core/deps/libzip/lib/zip_strerror.c @@ -1,9 +1,9 @@ /* zip_sterror.c -- get string representation of zip error - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_string.c b/core/deps/libzip/lib/zip_string.c index 2c6f42826..1c964491c 100644 --- a/core/deps/libzip/lib/zip_string.c +++ b/core/deps/libzip/lib/zip_string.c @@ -1,9 +1,9 @@ /* zip_string.c -- string handling (with encoding) - Copyright (C) 2012-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2012-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -150,7 +150,7 @@ _zip_string_new(const zip_uint8_t *raw, zip_uint16_t length, zip_flags_t flags, return NULL; } - memcpy(s->raw, raw, length); + (void)memcpy_s(s->raw, length + 1, raw, length); s->raw[length] = '\0'; s->length = length; s->encoding = ZIP_ENCODING_UNKNOWN; diff --git a/core/deps/libzip/lib/zip_unchange.c b/core/deps/libzip/lib/zip_unchange.c index 17e250099..d69a3dfe1 100644 --- a/core/deps/libzip/lib/zip_unchange.c +++ b/core/deps/libzip/lib/zip_unchange.c @@ -1,9 +1,9 @@ /* zip_unchange.c -- undo changes to file in zip archive - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -46,14 +46,18 @@ zip_unchange(zip_t *za, zip_uint64_t idx) { int _zip_unchange(zip_t *za, zip_uint64_t idx, int allow_duplicates) { zip_int64_t i; - const char *orig_name, *changed_name; + bool renamed; if (idx >= za->nentry) { zip_error_set(&za->error, ZIP_ER_INVAL, 0); return -1; } - if (!allow_duplicates && za->entry[idx].changes && (za->entry[idx].changes->changed & ZIP_DIRENT_FILENAME)) { + renamed = za->entry[idx].changes && (za->entry[idx].changes->changed & ZIP_DIRENT_FILENAME); + if (!allow_duplicates && (renamed || za->entry[idx].deleted)) { + const char *orig_name = NULL; + const char *changed_name = NULL; + if (za->entry[idx].orig != NULL) { if ((orig_name = _zip_get_name(za, idx, ZIP_FL_UNCHANGED, &za->error)) == NULL) { return -1; @@ -65,12 +69,11 @@ _zip_unchange(zip_t *za, zip_uint64_t idx, int allow_duplicates) { return -1; } } - else { - orig_name = NULL; - } - if ((changed_name = _zip_get_name(za, idx, 0, &za->error)) == NULL) { - return -1; + if (renamed) { + if ((changed_name = _zip_get_name(za, idx, 0, &za->error)) == NULL) { + return -1; + } } if (orig_name) { @@ -78,9 +81,11 @@ _zip_unchange(zip_t *za, zip_uint64_t idx, int allow_duplicates) { return -1; } } - if (_zip_hash_delete(za->names, (const zip_uint8_t *)changed_name, &za->error) == false) { - _zip_hash_delete(za->names, (const zip_uint8_t *)orig_name, NULL); - return -1; + if (changed_name) { + if (_zip_hash_delete(za->names, (const zip_uint8_t *)changed_name, &za->error) == false) { + _zip_hash_delete(za->names, (const zip_uint8_t *)orig_name, NULL); + return -1; + } } } diff --git a/core/deps/libzip/lib/zip_unchange_all.c b/core/deps/libzip/lib/zip_unchange_all.c index b8bfd70e9..34f3702e0 100644 --- a/core/deps/libzip/lib/zip_unchange_all.c +++ b/core/deps/libzip/lib/zip_unchange_all.c @@ -1,9 +1,9 @@ /* zip_unchange.c -- undo changes to all files in zip archive - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_unchange_archive.c b/core/deps/libzip/lib/zip_unchange_archive.c index ebf2bd58a..56a8e31f6 100644 --- a/core/deps/libzip/lib/zip_unchange_archive.c +++ b/core/deps/libzip/lib/zip_unchange_archive.c @@ -1,9 +1,9 @@ /* zip_unchange_archive.c -- undo global changes to ZIP archive - Copyright (C) 2006-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2006-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_unchange_data.c b/core/deps/libzip/lib/zip_unchange_data.c index 2a393add8..6bdecd187 100644 --- a/core/deps/libzip/lib/zip_unchange_data.c +++ b/core/deps/libzip/lib/zip_unchange_data.c @@ -1,9 +1,9 @@ /* zip_unchange_data.c -- undo helper function - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_utf-8.c b/core/deps/libzip/lib/zip_utf-8.c index 51a47221e..678912f6b 100644 --- a/core/deps/libzip/lib/zip_utf-8.c +++ b/core/deps/libzip/lib/zip_utf-8.c @@ -1,9 +1,9 @@ /* zip_utf-8.c -- UTF-8 support functions for libzip - Copyright (C) 2011-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2011-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/lib/zip_winzip_aes.c b/core/deps/libzip/lib/zip_winzip_aes.c index 75b279a96..ce269036c 100644 --- a/core/deps/libzip/lib/zip_winzip_aes.c +++ b/core/deps/libzip/lib/zip_winzip_aes.c @@ -1,9 +1,9 @@ /* zip_winzip_aes.c -- Winzip AES de/encryption backend routines - Copyright (C) 2017-2020 Dieter Baron and Thomas Klausner + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -125,7 +125,7 @@ _zip_winzip_aes_new(const zip_uint8_t *password, zip_uint64_t password_length, c } if (password_verify) { - memcpy(password_verify, buffer + (2 * key_size / 8), WINZIP_AES_PASSWORD_VERIFY_LENGTH); + (void)memcpy_s(password_verify, WINZIP_AES_PASSWORD_VERIFY_LENGTH, buffer + (2 * key_size / 8), WINZIP_AES_PASSWORD_VERIFY_LENGTH); } return ctx; diff --git a/core/deps/libzip/lib/zipint.h b/core/deps/libzip/lib/zipint.h index 6036d2e42..4887b6c5c 100644 --- a/core/deps/libzip/lib/zipint.h +++ b/core/deps/libzip/lib/zipint.h @@ -3,10 +3,10 @@ /* zipint.h -- internal declarations. - Copyright (C) 1999-2020 Dieter Baron and Thomas Klausner + Copyright (C) 1999-2022 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions @@ -42,7 +42,9 @@ #include #endif -#ifndef _ZIP_COMPILING_DEPRECATED +#ifdef _ZIP_COMPILING_DEPRECATED +#define ZIP_DEPRECATED(x) +#else #define ZIP_DISABLE_DEPRECATED #endif @@ -67,6 +69,12 @@ #define EF_WINZIP_AES_SIZE 7 #define MAX_DATA_DESCRIPTOR_LENGTH 24 +#define TORRENTZIP_SIGNATURE "TORRENTZIPPED-" +#define TORRENTZIP_SIGNATURE_LENGTH 14 +#define TORRENTZIP_CRC_LENGTH 8 +#define TORRENTZIP_MEM_LEVEL 8 +#define TORRENTZIP_COMPRESSION_FLAGS ZIP_UINT16_MAX + #define ZIP_CRYPTO_PKWARE_HEADERLEN 12 #define ZIP_CM_REPLACED_DEFAULT (-2) @@ -76,7 +84,6 @@ #define WINZIP_AES_MAX_HEADER_LENGTH (16 + WINZIP_AES_PASSWORD_VERIFY_LENGTH) #define AES_BLOCK_SIZE 16 #define HMAC_LENGTH 10 -#define SHA1_LENGTH 20 #define SALT_LENGTH(method) ((method) == ZIP_EM_AES_128 ? 8 : ((method) == ZIP_EM_AES_192 ? 12 : 16)) #define ZIP_CM_IS_DEFAULT(x) ((x) == ZIP_CM_DEFAULT || (x) == ZIP_CM_REPLACED_DEFAULT) @@ -122,11 +129,11 @@ enum zip_compression_status { typedef enum zip_compression_status zip_compression_status_t; struct zip_compression_algorithm { - /* Return maxiumum compressed size for uncompressed data of given size. */ + /* Return maximum compressed size for uncompressed data of given size. */ zip_uint64_t (*maximum_compressed_size)(zip_uint64_t uncompressed_size); /* called once to create new context */ - void *(*allocate)(zip_uint16_t method, int compression_flags, zip_error_t *error); + void *(*allocate)(zip_uint16_t method, zip_uint32_t compression_flags, zip_error_t *error); /* called once to free context */ void (*deallocate)(void *ctx); @@ -170,25 +177,65 @@ const zip_uint8_t *zip_get_extra_field_by_id(zip_t *, int, int, zip_uint16_t, in user-supplied compression/encryption implementation is finished. Thus we will keep it private for now. */ -typedef zip_int64_t (*zip_source_layered_callback)(zip_source_t *, void *, void *, zip_uint64_t, enum zip_source_cmd); -zip_source_t *zip_source_compress(zip_t *za, zip_source_t *src, zip_int32_t cm, int compression_flags); -zip_source_t *zip_source_crc(zip_t *, zip_source_t *, int); +zip_source_t *zip_source_compress(zip_t *za, zip_source_t *src, zip_int32_t cm, zip_uint32_t compression_flags); +zip_source_t *zip_source_crc_create(zip_source_t *, int, zip_error_t *error); zip_source_t *zip_source_decompress(zip_t *za, zip_source_t *src, zip_int32_t cm); -zip_source_t *zip_source_layered(zip_t *, zip_source_t *, zip_source_layered_callback, void *); -zip_source_t *zip_source_layered_create(zip_source_t *src, zip_source_layered_callback cb, void *ud, zip_error_t *error); zip_source_t *zip_source_pkware_decode(zip_t *, zip_source_t *, zip_uint16_t, int, const char *); zip_source_t *zip_source_pkware_encode(zip_t *, zip_source_t *, zip_uint16_t, int, const char *); int zip_source_remove(zip_source_t *); zip_int64_t zip_source_supports(zip_source_t *src); -zip_source_t *zip_source_window(zip_t *, zip_source_t *, zip_uint64_t, zip_uint64_t); +bool zip_source_supports_reopen(zip_source_t *src); zip_source_t *zip_source_winzip_aes_decode(zip_t *, zip_source_t *, zip_uint16_t, int, const char *); zip_source_t *zip_source_winzip_aes_encode(zip_t *, zip_source_t *, zip_uint16_t, int, const char *); zip_source_t *zip_source_buffer_with_attributes(zip_t *za, const void *data, zip_uint64_t len, int freep, zip_file_attributes_t *attributes); +zip_source_t *zip_source_buffer_with_attributes_create(const void *data, zip_uint64_t len, int freep, zip_file_attributes_t *attributes, zip_error_t *error); /* error source for layered sources */ enum zip_les { ZIP_LES_NONE, ZIP_LES_UPPER, ZIP_LES_LOWER, ZIP_LES_INVAL }; +#define ZIP_DETAIL_ET_GLOBAL 0 +#define ZIP_DETAIL_ET_ENTRY 1 + +struct _zip_err_info { + int type; + const char *description; +}; + +extern const struct _zip_err_info _zip_err_str[]; +extern const int _zip_err_str_count; +extern const struct _zip_err_info _zip_err_details[]; +extern const int _zip_err_details_count; + +/* macros for libzip-internal errors */ +#define MAX_DETAIL_INDEX 0x7fffff +#define MAKE_DETAIL_WITH_INDEX(error, index) ((((index) > MAX_DETAIL_INDEX) ? MAX_DETAIL_INDEX : (int)(index)) << 8 | (error)) +#define GET_INDEX_FROM_DETAIL(error) (((error) >> 8) & MAX_DETAIL_INDEX) +#define GET_ERROR_FROM_DETAIL(error) ((error) & 0xff) +#define ADD_INDEX_TO_DETAIL(error, index) MAKE_DETAIL_WITH_INDEX(GET_ERROR_FROM_DETAIL(error), (index)) + +/* error code for libzip-internal errors */ +#define ZIP_ER_DETAIL_NO_DETAIL 0 /* G no detail */ +#define ZIP_ER_DETAIL_CDIR_OVERLAPS_EOCD 1 /* G central directory overlaps EOCD, or there is space between them */ +#define ZIP_ER_DETAIL_COMMENT_LENGTH_INVALID 2 /* G archive comment length incorrect */ +#define ZIP_ER_DETAIL_CDIR_LENGTH_INVALID 3 /* G central directory length invalid */ +#define ZIP_ER_DETAIL_CDIR_ENTRY_INVALID 4 /* E central header invalid */ +#define ZIP_ER_DETAIL_CDIR_WRONG_ENTRIES_COUNT 5 /* G central directory count of entries is incorrect */ +#define ZIP_ER_DETAIL_ENTRY_HEADER_MISMATCH 6 /* E local and central headers do not match */ +#define ZIP_ER_DETAIL_EOCD_LENGTH_INVALID 7 /* G wrong EOCD length */ +#define ZIP_ER_DETAIL_EOCD64_OVERLAPS_EOCD 8 /* G EOCD64 overlaps EOCD, or there is space between them */ +#define ZIP_ER_DETAIL_EOCD64_WRONG_MAGIC 9 /* G EOCD64 magic incorrect */ +#define ZIP_ER_DETAIL_EOCD64_MISMATCH 10 /* G EOCD64 and EOCD do not match */ +#define ZIP_ER_DETAIL_CDIR_INVALID 11 /* G invalid value in central directory */ +#define ZIP_ER_DETAIL_VARIABLE_SIZE_OVERFLOW 12 /* E variable size fields overflow header */ +#define ZIP_ER_DETAIL_INVALID_UTF8_IN_FILENAME 13 /* E invalid UTF-8 in filename */ +#define ZIP_ER_DETAIL_INVALID_UTF8_IN_COMMENT 13 /* E invalid UTF-8 in comment */ +#define ZIP_ER_DETAIL_INVALID_ZIP64_EF 14 /* E invalid Zip64 extra field */ +#define ZIP_ER_DETAIL_INVALID_WINZIPAES_EF 14 /* E invalid WinZip AES extra field */ +#define ZIP_ER_DETAIL_EF_TRAILING_GARBAGE 15 /* E garbage at end of extra fields */ +#define ZIP_ER_DETAIL_INVALID_EF_LENGTH 16 /* E extra field length is invalid */ +#define ZIP_ER_DETAIL_INVALID_FILE_LENGTH 17 /* E file length in header doesn't match actual file length */ + /* directory entry: general purpose bit flags */ #define ZIP_GPBF_ENCRYPTED 0x0001u /* is encrypted */ @@ -258,14 +305,14 @@ struct zip { zip_hash_t *names; /* hash table for name lookup */ zip_progress_t *progress; /* progress callback for zip_close() */ + + zip_uint32_t* write_crc; /* have _zip_write() compute CRC */ }; /* file in zip archive, part of API */ struct zip_file { - zip_t *za; /* zip archive containing this file */ zip_error_t error; /* error information */ - bool eof; zip_source_t *src; /* data source */ }; @@ -304,7 +351,7 @@ struct zip_dirent { zip_uint32_t ext_attrib; /* (c) external file attributes */ zip_uint64_t offset; /* (c) offset of local header */ - zip_uint16_t compression_level; /* level of compression to use (never valid in orig) */ + zip_uint32_t compression_level; /* level of compression to use (never valid in orig) */ zip_uint16_t encryption_method; /* encryption method, computed from other fields */ char *password; /* file specific encryption password */ }; @@ -414,7 +461,7 @@ struct zip_buffer { struct zip_filelist { zip_uint64_t idx; - /* TODO const char *name; */ + const char *name; }; typedef struct zip_filelist zip_filelist_t; @@ -427,10 +474,6 @@ struct _zip_pkware_keys { }; typedef struct _zip_pkware_keys zip_pkware_keys_t; -extern const char *const _zip_err_str[]; -extern const int _zip_nerr_str; -extern const int _zip_err_type[]; - #define ZIP_MAX(a, b) ((a) > (b) ? (a) : (b)) #define ZIP_MIN(a, b) ((a) < (b) ? (a) : (b)) @@ -439,6 +482,8 @@ extern const int _zip_err_type[]; #define ZIP_ENTRY_HAS_CHANGES(e) (ZIP_ENTRY_DATA_CHANGED(e) || (e)->deleted || ZIP_ENTRY_CHANGED((e), ZIP_DIRENT_ALL)) #define ZIP_IS_RDONLY(za) ((za)->ch_flags & ZIP_AFL_RDONLY) +#define ZIP_IS_TORRENTZIP(za) ((za)->flags & ZIP_AFL_IS_TORRENTZIP) +#define ZIP_WANT_TORRENTZIP(za) ((za)->ch_flags & ZIP_AFL_WANT_TORRENTZIP) #ifdef HAVE_EXPLICIT_MEMSET @@ -493,8 +538,11 @@ void _zip_dirent_finalize(zip_dirent_t *); void _zip_dirent_init(zip_dirent_t *); bool _zip_dirent_needs_zip64(const zip_dirent_t *, zip_flags_t); zip_dirent_t *_zip_dirent_new(void); +bool zip_dirent_process_ef_zip64(zip_dirent_t * zde, const zip_uint8_t * ef, zip_uint64_t got_len, bool local, zip_error_t * error); zip_int64_t _zip_dirent_read(zip_dirent_t *zde, zip_source_t *src, zip_buffer_t *buffer, bool local, zip_error_t *error); void _zip_dirent_set_version_needed(zip_dirent_t *de, bool force_zip64); +void zip_dirent_torrentzip_normalize(zip_dirent_t *de); + zip_int32_t _zip_dirent_size(zip_source_t *src, zip_uint16_t, zip_error_t *); int _zip_dirent_write(zip_t *za, zip_dirent_t *dirent, zip_flags_t flags); @@ -516,7 +564,6 @@ void _zip_error_clear(zip_error_t *); void _zip_error_get(const zip_error_t *, int *, int *); void _zip_error_copy(zip_error_t *dst, const zip_error_t *src); -void _zip_error_set_from_source(zip_error_t *, zip_source_t *); const zip_uint8_t *_zip_extract_extra_field_by_id(zip_error_t *, zip_uint16_t, int, const zip_uint8_t *, zip_uint16_t, zip_uint16_t *); @@ -538,7 +585,7 @@ zip_hash_t *_zip_hash_new(zip_error_t *error); bool _zip_hash_reserve_capacity(zip_hash_t *hash, zip_uint64_t capacity, zip_error_t *error); bool _zip_hash_revert(zip_hash_t *hash, zip_error_t *error); -int _zip_mkstempm(char *path, int mode); +int _zip_mkstempm(char *path, int mode, bool create_file); zip_t *_zip_open(zip_source_t *, unsigned int, zip_error_t *); @@ -569,16 +616,15 @@ bool _zip_source_had_error(zip_source_t *); void _zip_source_invalidate(zip_source_t *src); zip_source_t *_zip_source_new(zip_error_t *error); int _zip_source_set_source_archive(zip_source_t *, zip_t *); -zip_source_t *_zip_source_window_new(zip_source_t *src, zip_uint64_t start, zip_uint64_t length, zip_stat_t *st, zip_file_attributes_t *attributes, zip_t *source_archive, zip_uint64_t source_index, zip_error_t *error); -zip_source_t *_zip_source_zip_new(zip_t *, zip_t *, zip_uint64_t, zip_flags_t, zip_uint64_t, zip_uint64_t, const char *); +zip_source_t *_zip_source_window_new(zip_source_t *src, zip_uint64_t start, zip_int64_t length, zip_stat_t *st, zip_uint64_t st_invalid, zip_file_attributes_t *attributes, zip_t *source_archive, zip_uint64_t source_index, bool take_ownership, zip_error_t *error); int _zip_stat_merge(zip_stat_t *dst, const zip_stat_t *src, zip_error_t *error); -int _zip_string_equal(const zip_string_t *, const zip_string_t *); -void _zip_string_free(zip_string_t *); -zip_uint32_t _zip_string_crc32(const zip_string_t *); -const zip_uint8_t *_zip_string_get(zip_string_t *, zip_uint32_t *, zip_flags_t, zip_error_t *); -zip_uint16_t _zip_string_length(const zip_string_t *); -zip_string_t *_zip_string_new(const zip_uint8_t *, zip_uint16_t, zip_flags_t, zip_error_t *); +int _zip_string_equal(const zip_string_t *a, const zip_string_t *b); +void _zip_string_free(zip_string_t *string); +zip_uint32_t _zip_string_crc32(const zip_string_t *string); +const zip_uint8_t *_zip_string_get(zip_string_t *string, zip_uint32_t *lenp, zip_flags_t flags, zip_error_t *error); +zip_uint16_t _zip_string_length(const zip_string_t *string); +zip_string_t *_zip_string_new(const zip_uint8_t *raw, zip_uint16_t length, zip_flags_t flags, zip_error_t *error); int _zip_string_write(zip_t *za, const zip_string_t *string); bool _zip_winzip_aes_decrypt(zip_winzip_aes_t *ctx, zip_uint8_t *data, zip_uint64_t length); bool _zip_winzip_aes_encrypt(zip_winzip_aes_t *ctx, zip_uint8_t *data, zip_uint64_t length); diff --git a/core/deps/libzip/libzip-config.cmake.in b/core/deps/libzip/libzip-config.cmake.in index 5b9aa5580..806153036 100644 --- a/core/deps/libzip/libzip-config.cmake.in +++ b/core/deps/libzip/libzip-config.cmake.in @@ -1,9 +1,43 @@ @PACKAGE_INIT@ -# only needed for static library, and doesn't work as-is -#include(CMakeFindDependencyMacro) -#find_dependency(ZLIB::ZLIB) -# how to handle the optional dependencies? +# We need to supply transitive dependencies if this config is for a static library +set(IS_SHARED @BUILD_SHARED_LIBS@) +if (NOT IS_SHARED) + include(CMakeFindDependencyMacro) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/modules") + + set(ENABLE_BZIP2 @BZIP2_FOUND@) + set(ENABLE_LZMA @LIBLZMA_FOUND@) + set(ENABLE_ZSTD @ZSTD_FOUND@) + set(ENABLE_GNUTLS @GNUTLS_FOUND@) + set(ENABLE_MBEDTLS @MBEDTLS_FOUND@) + set(ENABLE_OPENSSL @OPENSSL_FOUND@) + + find_dependency(ZLIB 1.1.2) + if(ENABLE_BZIP2) + find_dependency(BZip2) + endif() + + if(ENABLE_LZMA) + find_dependency(LibLZMA 5.2) + endif() + + if(ENABLE_ZSTD) + find_dependency(zstd 1.3.6) + endif() + + if(ENABLE_GNUTLS) + find_dependency(Nettle 3.0) + find_dependency(GnuTLS) + endif() + if(ENABLE_MBEDTLS) + find_dependency(MbedTLS 1.0) + endif() + if(ENABLE_OPENSSL) + find_dependency(OpenSSL) + endif() +endif() + # Provide all our library targets to users. include("${CMAKE_CURRENT_LIST_DIR}/libzip-targets.cmake") diff --git a/core/deps/libzip/libzip.pc.in b/core/deps/libzip/libzip.pc.in index 58b3dca7c..d51b0ab63 100644 --- a/core/deps/libzip/libzip.pc.in +++ b/core/deps/libzip/libzip.pc.in @@ -1,10 +1,10 @@ -prefix=@prefix@ -exec_prefix=@exec_prefix@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} bindir=@bindir@ libdir=@libdir@ includedir=@includedir@ -zipcmp=@bindir@/zipcmp +zipcmp=${bindir}/zipcmp Name: libzip Description: library for handling zip archives @@ -12,4 +12,3 @@ Version: @PROJECT_VERSION@ Libs: @PKG_CONFIG_RPATH@ -L${libdir} -lzip Libs.private: @LIBS@ Cflags: -I${includedir} - diff --git a/core/deps/libzip/man/CMakeLists.txt b/core/deps/libzip/man/CMakeLists.txt index 90d462a51..fe3f091f7 100644 --- a/core/deps/libzip/man/CMakeLists.txt +++ b/core/deps/libzip/man/CMakeLists.txt @@ -54,6 +54,7 @@ set(MAN_PAGES zip_libzip_version.3 zip_name_locate.3 zip_open.3 + zip_register_cancel_callback_with_state.3 zip_register_progress_callback.3 zip_register_progress_callback_with_state.3 zip_rename.3 @@ -74,6 +75,8 @@ set(MAN_PAGES zip_source_free.3 zip_source_function.3 zip_source_is_deleted.3 + zip_source_is_seekable.3 + zip_source_layered.3 zip_source_keep.3 zip_source_make_command_bitmap.3 zip_source_open.3 @@ -88,8 +91,10 @@ set(MAN_PAGES zip_source_win32a.3 zip_source_win32handle.3 zip_source_win32w.3 + zip_source_window_create.3 zip_source_write.3 zip_source_zip.3 + zip_source_zip_file.3 zip_stat.3 zip_stat_init.3 zip_unchange.3 @@ -114,7 +119,7 @@ foreach(MAN_PAGE ${MAN_PAGES}) #configure_file(${SOURCE_FILE} ${MAN_PAGE} COPYONLY) add_custom_command(OUTPUT ${MAN_PAGE} DEPENDS ${SOURCE_FILE} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${PROJECT_SOURCE_DIR}/man/${SOURCE_FILE} ${PROJECT_BINARY_DIR}/man/${MAN_PAGE} + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_SOURCE_DIR}/${SOURCE_FILE} ${CMAKE_CURRENT_BINARY_DIR}/${MAN_PAGE} COMMENT "Preparing ${MAN_PAGE}" ) @@ -123,18 +128,20 @@ foreach(MAN_PAGE ${MAN_PAGES}) string(REGEX REPLACE "[1-9]$" "mdoc" MDOC_FILE ${MAN_PAGE}) # html re-generation - add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/man/${HTML_FILE} + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${HTML_FILE} DEPENDS ${MDOC_FILE} - COMMAND ${CMAKE_COMMAND} -DIN=${MDOC_FILE} -DOUT=${HTML_FILE} -DDIR=${PROJECT_SOURCE_DIR}/man -P ${PROJECT_SOURCE_DIR}/man/update-html.cmake + COMMAND ${CMAKE_COMMAND} -DIN=${CMAKE_CURRENT_SOURCE_DIR}/${MDOC_FILE} -DOUT=${CMAKE_CURRENT_BINARY_DIR}/${HTML_FILE} -P ${CMAKE_CURRENT_SOURCE_DIR}/update-html.cmake + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/${HTML_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/${HTML_FILE} ) - list(APPEND UPDATEHTML ${PROJECT_SOURCE_DIR}/man/${HTML_FILE}) + list(APPEND UPDATEHTML ${CMAKE_CURRENT_BINARY_DIR}/${HTML_FILE}) # man re-generation - add_custom_command(OUTPUT ${PROJECT_SOURCE_DIR}/man/${MAN_FILE} + add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${MAN_FILE} DEPENDS ${MDOC_FILE} - COMMAND ${CMAKE_COMMAND} -DIN=${MDOC_FILE} -DOUT=${MAN_FILE} -DDIR=${PROJECT_SOURCE_DIR}/man -P ${PROJECT_SOURCE_DIR}/man/update-man.cmake + COMMAND ${CMAKE_COMMAND} -DIN=${CMAKE_CURRENT_SOURCE_DIR}/${MDOC_FILE} -DOUT=${CMAKE_CURRENT_BINARY_DIR}/${MAN_FILE} -P ${CMAKE_CURRENT_SOURCE_DIR}/update-man.cmake + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_CURRENT_BINARY_DIR}/${MAN_FILE} ${CMAKE_CURRENT_SOURCE_DIR}/${MAN_FILE} ) - list(APPEND UPDATEMAN ${PROJECT_SOURCE_DIR}/man/${MAN_FILE}) + list(APPEND UPDATEMAN ${CMAKE_CURRENT_BINARY_DIR}/${MAN_FILE}) endforeach() add_custom_target(man ALL DEPENDS ${MAN_PAGES}) add_custom_target(update-man DEPENDS ${UPDATEMAN}) diff --git a/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.html b/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.html index bea0f3c6f..cae3b3f48 100644 --- a/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.html +++ b/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.html @@ -5,7 +5,7 @@ Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.man b/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.man index 0e23ebd2a..cd49d5fde 100644 --- a/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.man +++ b/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.mdoc b/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.mdoc index 2735964e1..3f8fb40ea 100644 --- a/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.mdoc +++ b/core/deps/libzip/man/ZIP_SOURCE_GET_ARGS.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/fix-man-links.sh b/core/deps/libzip/man/fix-man-links.sh deleted file mode 100644 index 53b7b0766..000000000 --- a/core/deps/libzip/man/fix-man-links.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/sh - -# -LINKBASE='http://pubs.opengroup.org/onlinepubs/9699919799/functions/' - -sed -E -e 's,(),\1'"$LINKBASE"'\2\3,g' -e "s,$LINKBASE"'(libzip|zip),\1,g' diff --git a/core/deps/libzip/man/libzip.html b/core/deps/libzip/man/libzip.html index bda14619d..693b04fd3 100644 --- a/core/deps/libzip/man/libzip.html +++ b/core/deps/libzip/man/libzip.html @@ -2,10 +2,10 @@ + + + + ZIP_REGISTER_CANCEL_CALLBACK_WITH_STATE(3) + + + + + + + + +
ZIP_REGISTER_CANCEL_CALLBACK_WITH_STATE(3)Library Functions ManualZIP_REGISTER_CANCEL_CALLBACK_WITH_STATE(3)
+
+
+

+zip_register_cancel_callback_with_state — +
allow cancelling during zip_close
+
+
+

+libzip (-lzip) +
+
+

+#include <zip.h> +

typedef int (*zip_cancel_callback)(zip_t *, void + *);

+

void +
+ zip_register_cancel_callback_with_state(zip_t + *archive, + zip_cancel_callback + callback, void + (*ud_free)(void *), void + *ud);

+
+
+

+This function can be used to cancel writing of a zip archive during + zip_close(3). +

The + zip_register_cancel_callback_with_state() function + registers a callback function callback for the zip + archive archive. The ud_free + function is called during cleanup for deleting the userdata supplied in + ud.

+

The callback function is called during + zip_close(3) in regular intervals + (after every zip archive entry that's completely written to disk, and while + writing data for entries) with zip archive archive and + the user-provided user-data ud as arguments. When the + callback function returns a non-zero value, writing is cancelled and + zip_close(3) returns an error.

+

The callback function should be fast, since it will be called + often.

+
+
+

+libzip(3), + zip_close(3), + zip_register_progress_callback_with_state(3) +
+
+

+zip_register_cancel_callback_with_state() was added in + libzip 1.6.0. +
+
+

+Dieter Baron + <dillo@nih.at> and + Thomas Klausner + <tk@giga.or.at> +
+
+ + + + + +
June 18, 2022NiH
+ + diff --git a/core/deps/libzip/man/zip_register_cancel_callback_with_state.man b/core/deps/libzip/man/zip_register_cancel_callback_with_state.man new file mode 100644 index 000000000..28a46a45c --- /dev/null +++ b/core/deps/libzip/man/zip_register_cancel_callback_with_state.man @@ -0,0 +1,90 @@ +.\" Automatically generated from an mdoc input file. Do not edit. +.\" zip_register_cancel_callback_with_state.mdoc -- allow cancelling during zip_close +.\" Copyright (C) 2021-2022 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.TH "ZIP_REGISTER_CANCEL_CALLBACK_WITH_STATE" "3" "June 18, 2022" "NiH" "Library Functions Manual" +.nh +.if n .ad l +.SH "NAME" +\fBzip_register_cancel_callback_with_state\fR +\- allow cancelling during zip_close +.SH "LIBRARY" +libzip (-lzip) +.SH "SYNOPSIS" +\fB#include \fR +.sp +\fItypedef int (*zip_cancel_callback)(zip_t *, void *);\fR +.sp +\fIvoid\fR +.br +.PD 0 +.HP 4n +\fBzip_register_cancel_callback_with_state\fR(\fIzip_t\ *archive\fR, \fIzip_cancel_callback\ callback\fR, \fIvoid\ (*ud_free)(void\ *)\fR, \fIvoid\ *ud\fR); +.PD +.SH "DESCRIPTION" +This function can be used to cancel writing of a zip archive during +zip_close(3). +.PP +The +\fBzip_register_cancel_callback_with_state\fR() +function registers a callback function +\fIcallback\fR +for the zip archive +\fIarchive\fR. +The +\fIud_free\fR +function is called during cleanup for deleting the userdata supplied in +\fIud\fR. +.PP +The callback function is called during +zip_close(3) +in regular intervals (after every zip archive entry that's completely +written to disk, and while writing data for entries) with zip archive +\fIarchive\fR +and the user-provided user-data +\fIud\fR +as arguments. +When the callback function returns a non-zero value, writing is cancelled and +zip_close(3) +returns an error. +.PP +The callback function should be fast, since it will be called often. +.SH "SEE ALSO" +libzip(3), +zip_close(3), +zip_register_progress_callback_with_state(3) +.SH "HISTORY" +\fBzip_register_cancel_callback_with_state\fR() +was added in libzip 1.6.0. +.SH "AUTHORS" +Dieter Baron <\fIdillo@nih.at\fR> +and +Thomas Klausner <\fItk@giga.or.at\fR> diff --git a/core/deps/libzip/man/zip_register_cancel_callback_with_state.mdoc b/core/deps/libzip/man/zip_register_cancel_callback_with_state.mdoc new file mode 100644 index 000000000..b0712cbbb --- /dev/null +++ b/core/deps/libzip/man/zip_register_cancel_callback_with_state.mdoc @@ -0,0 +1,84 @@ +.\" zip_register_cancel_callback_with_state.mdoc -- allow cancelling during zip_close +.\" Copyright (C) 2021-2022 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd June 18, 2022 +.Dt ZIP_REGISTER_CANCEL_CALLBACK_WITH_STATE 3 +.Os +.Sh NAME +.Nm zip_register_cancel_callback_with_state +.Nd allow cancelling during zip_close +.Sh LIBRARY +libzip (-lzip) +.Sh SYNOPSIS +.In zip.h +.Vt typedef int (*zip_cancel_callback)(zip_t *, void *); +.Ft void +.Fn zip_register_cancel_callback_with_state "zip_t *archive" "zip_cancel_callback callback" "void (*ud_free)(void *)" "void *ud" +.Sh DESCRIPTION +This function can be used to cancel writing of a zip archive during +.Xr zip_close 3 . +.Pp +The +.Fn zip_register_cancel_callback_with_state +function registers a callback function +.Ar callback +for the zip archive +.Ar archive . +The +.Ar ud_free +function is called during cleanup for deleting the userdata supplied in +.Ar ud . +.Pp +The callback function is called during +.Xr zip_close 3 +in regular intervals (after every zip archive entry that's completely +written to disk, and while writing data for entries) with zip archive +.Ar archive +and the user-provided user-data +.Ar ud +as arguments. +When the callback function returns a non-zero value, writing is cancelled and +.Xr zip_close 3 +returns an error. +.Pp +The callback function should be fast, since it will be called often. +.Sh SEE ALSO +.Xr libzip 3 , +.Xr zip_close 3 , +.Xr zip_register_progress_callback_with_state 3 +.Sh HISTORY +.Fn zip_register_cancel_callback_with_state +was added in libzip 1.6.0. +.Sh AUTHORS +.An -nosplit +.An Dieter Baron Aq Mt dillo@nih.at +and +.An Thomas Klausner Aq Mt tk@giga.or.at diff --git a/core/deps/libzip/man/zip_register_progress_callback.html b/core/deps/libzip/man/zip_register_progress_callback.html index e61b7ceb9..5ba547b0b 100644 --- a/core/deps/libzip/man/zip_register_progress_callback.html +++ b/core/deps/libzip/man/zip_register_progress_callback.html @@ -5,7 +5,7 @@ Copyright (C) 2016-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_register_progress_callback.man b/core/deps/libzip/man/zip_register_progress_callback.man index 3d0e5b94d..2a1ed23d9 100644 --- a/core/deps/libzip/man/zip_register_progress_callback.man +++ b/core/deps/libzip/man/zip_register_progress_callback.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2016-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_register_progress_callback.mdoc b/core/deps/libzip/man/zip_register_progress_callback.mdoc index 9fd0cd0d7..f512bb0b8 100644 --- a/core/deps/libzip/man/zip_register_progress_callback.mdoc +++ b/core/deps/libzip/man/zip_register_progress_callback.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2016-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_register_progress_callback_with_state.html b/core/deps/libzip/man/zip_register_progress_callback_with_state.html index 4faf331d5..086a16e0c 100644 --- a/core/deps/libzip/man/zip_register_progress_callback_with_state.html +++ b/core/deps/libzip/man/zip_register_progress_callback_with_state.html @@ -2,10 +2,10 @@ + + + + ZIP_SOURCE_IS_SEEKABLE(3) + + + + + + + + +
ZIP_SOURCE_IS_SEEKABLE(3)Library Functions ManualZIP_SOURCE_IS_SEEKABLE(3)
+
+
+

+zip_source_is_seekable — +
check if a source supports seeking
+
+
+

+libzip (-lzip) +
+
+

+#include <zip.h> +

int +
+ zip_source_is_seekable(zip_source_t + *source);

+
+
+

+The function zip_source_is_seekable() checks if + source supports seeking via + zip_source_seek(3). +
+
+

+If the source supports seeking, 1 is returned. Otherwise, 0 is returned. +
+
+

+libzip(3), + zip_source(3), + zip_source_seek(3) +
+
+

+zip_source_is_seekable() was added in libzip 1.10.0. +
+
+

+Dieter Baron + <dillo@nih.at> and + Thomas Klausner + <tk@giga.or.at> +
+
+ + + + + +
March 10, 2023NiH
+ + diff --git a/core/deps/libzip/man/zip_source_is_seekable.man b/core/deps/libzip/man/zip_source_is_seekable.man new file mode 100644 index 000000000..bd31d0fa4 --- /dev/null +++ b/core/deps/libzip/man/zip_source_is_seekable.man @@ -0,0 +1,70 @@ +.\" Automatically generated from an mdoc input file. Do not edit. +.\" zip_source_seek.mdoc -- set read offset in source +.\" Copyright (C) 2023 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.TH "ZIP_SOURCE_IS_SEEKABLE" "3" "March 10, 2023" "NiH" "Library Functions Manual" +.nh +.if n .ad l +.SH "NAME" +\fBzip_source_is_seekable\fR +\- check if a source supports seeking +.SH "LIBRARY" +libzip (-lzip) +.SH "SYNOPSIS" +\fB#include \fR +.sp +\fIint\fR +.br +.PD 0 +.HP 4n +\fBzip_source_is_seekable\fR(\fIzip_source_t\ *source\fR); +.PD +.SH "DESCRIPTION" +The function +\fBzip_source_is_seekable\fR() +checks if +\fIsource\fR +supports seeking via +zip_source_seek(3). +.SH "RETURN VALUES" +If the source supports seeking, 1 is returned. +Otherwise, 0 is returned. +.SH "SEE ALSO" +libzip(3), +zip_source(3), +zip_source_seek(3) +.SH "HISTORY" +\fBzip_source_is_seekable\fR() +was added in libzip 1.10.0. +.SH "AUTHORS" +Dieter Baron <\fIdillo@nih.at\fR> +and +Thomas Klausner <\fItk@giga.or.at\fR> diff --git a/core/deps/libzip/man/zip_source_is_seekable.mdoc b/core/deps/libzip/man/zip_source_is_seekable.mdoc new file mode 100644 index 000000000..ad90bc755 --- /dev/null +++ b/core/deps/libzip/man/zip_source_is_seekable.mdoc @@ -0,0 +1,65 @@ +.\" zip_source_seek.mdoc -- set read offset in source +.\" Copyright (C) 2023 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd March 10, 2023 +.Dt ZIP_SOURCE_IS_SEEKABLE 3 +.Os +.Sh NAME +.Nm zip_source_is_seekable +.Nd check if a source supports seeking +.Sh LIBRARY +libzip (-lzip) +.Sh SYNOPSIS +.In zip.h +.Ft int +.Fn zip_source_is_seekable "zip_source_t *source" +.Sh DESCRIPTION +The function +.Fn zip_source_is_seekable +checks if +.Fa source +supports seeking via +.Xr zip_source_seek 3 . +.Sh RETURN VALUES +If the source supports seeking, 1 is returned. +Otherwise, 0 is returned. +.Sh SEE ALSO +.Xr libzip 3 , +.Xr zip_source 3 , +.Xr zip_source_seek 3 +.Sh HISTORY +.Fn zip_source_is_seekable +was added in libzip 1.10.0. +.Sh AUTHORS +.An -nosplit +.An Dieter Baron Aq Mt dillo@nih.at +and +.An Thomas Klausner Aq Mt tk@giga.or.at diff --git a/core/deps/libzip/man/zip_source_keep.html b/core/deps/libzip/man/zip_source_keep.html index fc7d1d16f..0219d64a0 100644 --- a/core/deps/libzip/man/zip_source_keep.html +++ b/core/deps/libzip/man/zip_source_keep.html @@ -5,7 +5,7 @@ Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_keep.man b/core/deps/libzip/man/zip_source_keep.man index 488673cbe..78a2d3958 100644 --- a/core/deps/libzip/man/zip_source_keep.man +++ b/core/deps/libzip/man/zip_source_keep.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_keep.mdoc b/core/deps/libzip/man/zip_source_keep.mdoc index 122c4f84a..0984d6c52 100644 --- a/core/deps/libzip/man/zip_source_keep.mdoc +++ b/core/deps/libzip/man/zip_source_keep.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_layered.html b/core/deps/libzip/man/zip_source_layered.html new file mode 100644 index 000000000..c53905360 --- /dev/null +++ b/core/deps/libzip/man/zip_source_layered.html @@ -0,0 +1,215 @@ + + + + + + + ZIP_SOURCE_LAYERED(3) + + + + + + + + +
ZIP_SOURCE_LAYERED(3)Library Functions ManualZIP_SOURCE_LAYERED(3)
+
+
+

+zip_source_layered, + zip_source_layered_create — +
create layered data source from function
+
+
+

+libzip (-lzip) +
+
+

+#include <zip.h> +

zip_source_t * +
+ zip_source_layered(zip_t + *archive, zip_source_t + *source, + zip_source_layered_callback + fn, void + *userdata);

+

zip_source_t * +
+ zip_source_layered_create(zip_source_t + *source, + zip_source_layered_callback + fn, void *userdata, + zip_error_t *error);

+
+
+

+The functions zip_source_layered() and + zip_source_layered_create() create a layered zip + source from the user-provided function fn, which must be + of the following type: +

typedef zip_int64_t + (*p_source_layered_callback)(zip_source_t + *source, void *userdata, void + *data, zip_uint64_t length, + zip_source_cmd_t cmd);

+

archive or error are + used for reporting errors and can be NULL.

+

When called by the library, the first argument is the + source of the lower layer, the second argument is the + userdata argument supplied to the function. The next + two arguments are a buffer data of size + length when data is passed in or expected to be + returned, or else NULL and 0. The last argument, + cmd, specifies which action the function should + perform.

+

See + zip_source_function(3) for + a description of the commands.

+

A layered source transforms the data or metadata of the source + below in some way. Layered sources can't support writing and are not + sufficient to cleanly add support for additional compression or encryption + methods. This may be revised in a later release of libzip.

+

On success, the layered source takes ownership of + source. The caller should not free it.

+

The interaction with the lower layer depends on the command:

+
+

ZIP_SOURCE_ACCEPT_EMPTY

+If the layered source supports this command, the lower layer is not called + automatically. Otherwise, the return value of the lower source is used. +
+
+

ZIP_SOURCE_CLOSE

+The lower layer is closed after the callback returns. +
+
+

ZIP_SOURCE_ERROR

+The lower layer is not called automatically. If you need to retrieve error + information from the lower layer, use + zip_error_set_from_source(3) + or + zip_source_pass_to_lower_layer(3). +
+
+

ZIP_SOURCE_FREE

+The lower layer is freed after the callback returns. +
+
+

ZIP_SOURCE_GET_FILE_ATTRIBUTES

+The attributes of the lower layer are merged with the attributes returned by the + callback: information set by the callback wins over the lower layer, with the + following exceptions: the higher version_needed is used, + and general_purpose_bit_flags are only overwritten if + the corresponding bit is set in + general_purpose_bit_mask. +
+
+

ZIP_SOURCE_OPEN

+The lower layer is opened before the callback is called. +
+
+

ZIP_SOURCE_READ

+The lower layer is not called automatically. +
+
+

ZIP_SOURCE_SEEK

+The lower layer is not called automatically. +
+
+

ZIP_SOURCE_STAT

+data contains the stat information from the lower layer + when the callback is called. +
+
+

ZIP_SOURCE_SUPPORTS

+data contains the bitmap of commands supported by the + lower layer when the callback is called. Since layered sources can't support + writing, all commands related to writing are stripped from the returned + support bitmap. +
+
+

ZIP_SOURCE_TELL

+The lower layer is not called automatically. +
+
+
+

+Upon successful completion, the created source is returned. Otherwise, + NULL is returned and the error code in + archive or error is set to + indicate the error (unless it is NULL). +
+
+

+zip_source_layered() fails if: +
+
[]
+
Required memory could not be allocated.
+
+
+
+

+libzip(3), + zip_file_add(3), + zip_file_attributes_init(3), + zip_file_replace(3), + zip_source(3), + zip_source_function(3), + zip_source_pass_to_lower_layer(3) +
+
+

+zip_source_layered() and + zip_source_layered_create() were added in libzip 1.10. +
+
+

+Dieter Baron + <dillo@nih.at> and + Thomas Klausner + <tk@giga.or.at> +
+
+ + + + + +
January 20, 2023NiH
+ + diff --git a/core/deps/libzip/man/zip_source_layered.man b/core/deps/libzip/man/zip_source_layered.man new file mode 100644 index 000000000..eaa4690b1 --- /dev/null +++ b/core/deps/libzip/man/zip_source_layered.man @@ -0,0 +1,173 @@ +.\" Automatically generated from an mdoc input file. Do not edit. +.\" zip_source_layered.mdoc -- create layered source from function +.\" Copyright (C) 2004-2022 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.TH "ZIP_SOURCE_LAYERED" "3" "January 20, 2023" "NiH" "Library Functions Manual" +.nh +.if n .ad l +.SH "NAME" +\fBzip_source_layered\fR, +\fBzip_source_layered_create\fR +\- create layered data source from function +.SH "LIBRARY" +libzip (-lzip) +.SH "SYNOPSIS" +\fB#include \fR +.sp +\fIzip_source_t *\fR +.br +.PD 0 +.HP 4n +\fBzip_source_layered\fR(\fIzip_t\ *archive\fR, \fIzip_source_t\ *source\fR, \fIzip_source_layered_callback\ fn\fR, \fIvoid\ *userdata\fR); +.PD +.PP +\fIzip_source_t *\fR +.br +.PD 0 +.HP 4n +\fBzip_source_layered_create\fR(\fIzip_source_t\ *source\fR, \fIzip_source_layered_callback\ fn\fR, \fIvoid\ *userdata\fR, \fIzip_error_t\ *error\fR); +.PD +.SH "DESCRIPTION" +The functions +\fBzip_source_layered\fR() +and +\fBzip_source_layered_create\fR() +create a layered zip source from the user-provided function +\fIfn\fR, +which must be of the following type: +.PP +\fItypedef zip_int64_t\fR +\fB\fR(*\zip_source_layered_callback\fR)\fP\fR(\fIzip_source_t\ *source\fR, \fIvoid\ *userdata\fR, \fIvoid\ *data\fR, \fIzip_uint64_t\ length\fR, \fIzip_source_cmd_t\ cmd\fR) +.PP +\fIarchive\fR +or +\fIerror\fR +are used for reporting errors and can be +\fRNULL\fR. +.PP +When called by the library, the first argument is the +\fIsource\fR +of the lower layer, the second argument is the +\fIuserdata\fR +argument supplied to the function. +The next two arguments are a buffer +\fIdata\fR +of size +\fIlength\fR +when data is passed in or expected to be returned, or else +\fRNULL\fR +and 0. +The last argument, +\fIcmd\fR, +specifies which action the function should perform. +.PP +See +zip_source_function(3) +for a description of the commands. +.PP +A layered source transforms the data or metadata of the source below in some way. +Layered sources can't support writing and are not sufficient to cleanly add support for additional compression or encryption methods. +This may be revised in a later release of libzip. +.PP +On success, the layered source takes ownership of +\fIsource\fR. +The caller should not free it. +.PP +The interaction with the lower layer depends on the command: +.SS "\fRZIP_SOURCE_ACCEPT_EMPTY\fR" +If the layered source supports this command, the lower layer is not called automatically. +Otherwise, the return value of the lower source is used. +.SS "\fRZIP_SOURCE_CLOSE\fR" +The lower layer is closed after the callback returns. +.SS "\fRZIP_SOURCE_ERROR\fR" +The lower layer is not called automatically. +If you need to retrieve error information from the lower layer, use +zip_error_set_from_source(3) +or +zip_source_pass_to_lower_layer(3). +.SS "\fRZIP_SOURCE_FREE\fR" +The lower layer is freed after the callback returns. +.SS "\fRZIP_SOURCE_GET_FILE_ATTRIBUTES\fR" +The attributes of the lower layer are merged with the attributes returned by the callback: information set by the callback wins over the lower layer, with the following exceptions: the higher +\fIversion_needed\fR +is used, and +\fIgeneral_purpose_bit_flags\fR +are only overwritten if the corresponding bit is set in +\fIgeneral_purpose_bit_mask\fR. +.SS "\fRZIP_SOURCE_OPEN\fR" +The lower layer is opened before the callback is called. +.SS "\fRZIP_SOURCE_READ\fR" +The lower layer is not called automatically. +.SS "\fRZIP_SOURCE_SEEK\fR" +The lower layer is not called automatically. +.SS "\fRZIP_SOURCE_STAT\fR" +\fIdata\fR +contains the stat information from the lower layer when the callback is called. +.SS "\fRZIP_SOURCE_SUPPORTS\fR" +\fIdata\fR +contains the bitmap of commands supported by the lower layer when the callback is called. +Since layered sources can't support writing, all commands related to writing are stripped from the returned support bitmap. +.SS "\fRZIP_SOURCE_TELL\fR" +The lower layer is not called automatically. +.SH "RETURN VALUES" +Upon successful completion, the created source is returned. +Otherwise, +\fRNULL\fR +is returned and the error code in +\fIarchive\fR +or +\fIerror\fR +is set to indicate the error (unless +it is +\fRNULL\fR). +.SH "ERRORS" +\fBzip_source_layered\fR() +fails if: +.TP 19n +[\fRZIP_ER_MEMORY\fR] +Required memory could not be allocated. +.SH "SEE ALSO" +libzip(3), +zip_file_add(3), +zip_file_attributes_init(3), +zip_file_replace(3), +zip_source(3), +zip_source_function(3), +zip_source_pass_to_lower_layer(3) +.SH "HISTORY" +\fBzip_source_layered\fR() +and +\fBzip_source_layered_create\fR() +were added in libzip 1.10. +.SH "AUTHORS" +Dieter Baron <\fIdillo@nih.at\fR> +and +Thomas Klausner <\fItk@giga.or.at\fR> diff --git a/core/deps/libzip/man/zip_source_layered.mdoc b/core/deps/libzip/man/zip_source_layered.mdoc new file mode 100644 index 000000000..aba25a5d7 --- /dev/null +++ b/core/deps/libzip/man/zip_source_layered.mdoc @@ -0,0 +1,167 @@ +.\" zip_source_layered.mdoc -- create layered source from function +.\" Copyright (C) 2004-2022 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd January 20, 2023 +.Dt ZIP_SOURCE_LAYERED 3 +.Os +.Sh NAME +.Nm zip_source_layered , +.Nm zip_source_layered_create +.Nd create layered data source from function +.Sh LIBRARY +libzip (-lzip) +.Sh SYNOPSIS +.In zip.h +.Ft zip_source_t * +.Fn zip_source_layered "zip_t *archive" "zip_source_t *source" "zip_source_layered_callback fn" "void *userdata" +.Ft zip_source_t * +.Fn zip_source_layered_create "zip_source_t *source" "zip_source_layered_callback fn" "void *userdata" "zip_error_t *error" +.Sh DESCRIPTION +The functions +.Fn zip_source_layered +and +.Fn zip_source_layered_create +create a layered zip source from the user-provided function +.Ar fn , +which must be of the following type: +.Pp +.Ft typedef zip_int64_t +.Fo \fR(*\zip_source_layered_callback\fR)\fP +.Fa "zip_source_t *source" "void *userdata" "void *data" "zip_uint64_t length" "zip_source_cmd_t cmd" +.Fc +.Pp +.Ar archive +or +.Ar error +are used for reporting errors and can be +.Dv NULL . +.Pp +When called by the library, the first argument is the +.Ar source +of the lower layer, the second argument is the +.Ar userdata +argument supplied to the function. +The next two arguments are a buffer +.Ar data +of size +.Ar length +when data is passed in or expected to be returned, or else +.Dv NULL +and 0. +The last argument, +.Ar cmd , +specifies which action the function should perform. +.Pp +See +.Xr zip_source_function 3 +for a description of the commands. +.Pp +A layered source transforms the data or metadata of the source below in some way. +Layered sources can't support writing and are not sufficient to cleanly add support for additional compression or encryption methods. +This may be revised in a later release of libzip. +.Pp +On success, the layered source takes ownership of +.Ar source . +The caller should not free it. +.Pp +The interaction with the lower layer depends on the command: +.El +.Ss Dv ZIP_SOURCE_ACCEPT_EMPTY +If the layered source supports this command, the lower layer is not called automatically. +Otherwise, the return value of the lower source is used. +.Ss Dv ZIP_SOURCE_CLOSE +The lower layer is closed after the callback returns. +.Ss Dv ZIP_SOURCE_ERROR +The lower layer is not called automatically. +If you need to retrieve error information from the lower layer, use +.Xr zip_error_set_from_source 3 +or +.Xr zip_source_pass_to_lower_layer 3 . +.Ss Dv ZIP_SOURCE_FREE +The lower layer is freed after the callback returns. +.Ss Dv ZIP_SOURCE_GET_FILE_ATTRIBUTES +The attributes of the lower layer are merged with the attributes returned by the callback: information set by the callback wins over the lower layer, with the following exceptions: the higher +.Ar version_needed +is used, and +.Ar general_purpose_bit_flags +are only overwritten if the corresponding bit is set in +.Ar general_purpose_bit_mask . +.Ss Dv ZIP_SOURCE_OPEN +The lower layer is opened before the callback is called. +.Ss Dv ZIP_SOURCE_READ +The lower layer is not called automatically. +.Ss Dv ZIP_SOURCE_SEEK +The lower layer is not called automatically. +.Ss Dv ZIP_SOURCE_STAT +.Ar data +contains the stat information from the lower layer when the callback is called. +.Ss Dv ZIP_SOURCE_SUPPORTS +.Ar data +contains the bitmap of commands supported by the lower layer when the callback is called. +Since layered sources can't support writing, all commands related to writing are stripped from the returned support bitmap. +.Ss Dv ZIP_SOURCE_TELL +The lower layer is not called automatically. +.Sh RETURN VALUES +Upon successful completion, the created source is returned. +Otherwise, +.Dv NULL +is returned and the error code in +.Ar archive +or +.Ar error +is set to indicate the error (unless +it is +.Dv NULL ) . +.Sh ERRORS +.Fn zip_source_layered +fails if: +.Bl -tag -width Er +.It Bq Er ZIP_ER_MEMORY +Required memory could not be allocated. +.El +.Sh SEE ALSO +.Xr libzip 3 , +.Xr zip_file_add 3 , +.Xr zip_file_attributes_init 3 , +.Xr zip_file_replace 3 , +.Xr zip_source 3 , +.Xr zip_source_function 3 , +.Xr zip_source_pass_to_lower_layer 3 +.Sh HISTORY +.Fn zip_source_layered +and +.Fn zip_source_layered_create +were added in libzip 1.10. +.Sh AUTHORS +.An -nosplit +.An Dieter Baron Aq Mt dillo@nih.at +and +.An Thomas Klausner Aq Mt tk@giga.or.at diff --git a/core/deps/libzip/man/zip_source_make_command_bitmap.html b/core/deps/libzip/man/zip_source_make_command_bitmap.html index d6ce945eb..28c3ad825 100644 --- a/core/deps/libzip/man/zip_source_make_command_bitmap.html +++ b/core/deps/libzip/man/zip_source_make_command_bitmap.html @@ -5,7 +5,7 @@ Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_make_command_bitmap.man b/core/deps/libzip/man/zip_source_make_command_bitmap.man index 3dc3ed5d0..6cba12f42 100644 --- a/core/deps/libzip/man/zip_source_make_command_bitmap.man +++ b/core/deps/libzip/man/zip_source_make_command_bitmap.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_make_command_bitmap.mdoc b/core/deps/libzip/man/zip_source_make_command_bitmap.mdoc index 28d296f2f..2b327e14d 100644 --- a/core/deps/libzip/man/zip_source_make_command_bitmap.mdoc +++ b/core/deps/libzip/man/zip_source_make_command_bitmap.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_open.html b/core/deps/libzip/man/zip_source_open.html index 9dc5a579a..443d9fb59 100644 --- a/core/deps/libzip/man/zip_source_open.html +++ b/core/deps/libzip/man/zip_source_open.html @@ -5,7 +5,7 @@ Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_open.man b/core/deps/libzip/man/zip_source_open.man index dc337baf6..2bf19207d 100644 --- a/core/deps/libzip/man/zip_source_open.man +++ b/core/deps/libzip/man/zip_source_open.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_open.mdoc b/core/deps/libzip/man/zip_source_open.mdoc index 00371f64f..521ba8808 100644 --- a/core/deps/libzip/man/zip_source_open.mdoc +++ b/core/deps/libzip/man/zip_source_open.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_pass_to_lower_layer.mdoc b/core/deps/libzip/man/zip_source_pass_to_lower_layer.mdoc new file mode 100644 index 000000000..aaafc236f --- /dev/null +++ b/core/deps/libzip/man/zip_source_pass_to_lower_layer.mdoc @@ -0,0 +1,64 @@ +.\" zip_source_pass_to_lower_layer.mdoc -- pass command to lower layer +.\" Copyright (C) 2022 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd December 5, 2022 +.Dt ZIP_SOURCE_PASS_TO_LOWER_LAYER 3 +.Os +.Sh NAME +.Nm zip_source_pass_to_lower_layer +.Nd pass command to lower layer +.Sh LIBRARY +libzip (-lzip) +.Sh SYNOPSIS +.In zip.h +.Ft zip_int64_t +.Fn zip_source_pass_to_lower_layer "zip_source_t *source" "void *data" "zip_uint64_t length" "zip_source_cmd_t command" +.Sh DESCRIPTION +The functions +.Fn zip_source_pass_to_lower_layer +is used in a layered source callback to pass commands for which you don't want to change the result to the lower layer. +You can use it in the +.Dv default +case of the callback. +.Sh RETURN VALUES +The return value is meant to be returned by the callback. +.Sh SEE ALSO +.Xr libzip 3 , +.Xr zip_source 3 , +.Xr zip_source_layered 3 +.Sh HISTORY +.Fn zip_source_pass_to_lower_layer +was added in libzip 1.10. +.Sh AUTHORS +.An -nosplit +.An Dieter Baron Aq Mt dillo@nih.at +and +.An Thomas Klausner Aq Mt tk@giga.or.at diff --git a/core/deps/libzip/man/zip_source_read.html b/core/deps/libzip/man/zip_source_read.html index 1269a42e6..0fbde0ad1 100644 --- a/core/deps/libzip/man/zip_source_read.html +++ b/core/deps/libzip/man/zip_source_read.html @@ -2,10 +2,10 @@ + + + + ZIP_SOURCE_WINDOW_CREATE(3) + + + + + + + + +
ZIP_SOURCE_WINDOW_CREATE(3)Library Functions ManualZIP_SOURCE_WINDOW_CREATE(3)
+
+
+

+zip_source_window_create — +
create zip data source overlay
+
+
+

+libzip (-lzip) +
+
+

+#include <zip.h> +

zip_source_t * +
+ zip_source_window_create(zip_source_t + *source, zip_uint64_t + start, zip_int64_t + len, zip_error_t + *error);

+
+
+

+The zip_source_window_create() function create a zip + source from an underlying zip source, restricting access to a particular + window starting at byte start and having size + len. If len is -1, the window + spans to the end of the underlying source. +

zip_source_window() and + zip_source_window_create() don't take ownership of + source. The caller is responsible for freeing it. + (This is different to other layered sources.)

+
+
+

+Upon successful completion, the created source is returned. Otherwise, + NULL is returned and the error code in + error is set to indicate the error. +
+
+

+zip_source_window_create() fails if: +
+
[]
+
src is NULL; there is an + integer overflow adding start and + len; or len is less than + -1.
+
[]
+
Required memory could not be allocated.
+
+
+
+

+libzip(3), + zip_source(3) + zip_source(3) +
+
+

+zip_source_window_create() was added in libzip 1.8.0. +
+
+

+Dieter Baron + <dillo@nih.at> and + Thomas Klausner + <tk@giga.or.at> +
+
+ + + + + +
April 29, 2021NiH
+ + diff --git a/core/deps/libzip/man/zip_source_window_create.man b/core/deps/libzip/man/zip_source_window_create.man new file mode 100644 index 000000000..78d35313b --- /dev/null +++ b/core/deps/libzip/man/zip_source_window_create.man @@ -0,0 +1,104 @@ +.\" Automatically generated from an mdoc input file. Do not edit. +.\" zip_source_window_create.mdoc -- create zip data source overlay +.\" Copyright (C) 2021 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.TH "ZIP_SOURCE_WINDOW_CREATE" "3" "April 29, 2021" "NiH" "Library Functions Manual" +.nh +.if n .ad l +.SH "NAME" +\fBzip_source_window_create\fR +\- create zip data source overlay +.SH "LIBRARY" +libzip (-lzip) +.SH "SYNOPSIS" +\fB#include \fR +.sp +\fIzip_source_t *\fR +.br +.PD 0 +.HP 4n +\fBzip_source_window_create\fR(\fIzip_source_t\ *source\fR, \fIzip_uint64_t\ start\fR, \fIzip_int64_t\ len\fR, \fIzip_error_t\ *error\fR); +.PD +.SH "DESCRIPTION" +The +\fBzip_source_window_create\fR() +function create a zip source from an underlying zip source, +restricting access to a particular window starting at byte +\fIstart\fR +and having size +\fIlen\fR. +If +\fIlen\fR +is \-1, the window spans to the end of the underlying source. +.PP +\fBzip_source_window\fR() +and +\fBzip_source_window_create\fR() +don't take ownership of +\fIsource\fR. +The caller is responsible for freeing it. +(This is different to other layered sources.) +.SH "RETURN VALUES" +Upon successful completion, the created source is returned. +Otherwise, +\fRNULL\fR +is returned and the error code in +\fIerror\fR +is set to indicate the error. +.SH "ERRORS" +\fBzip_source_window_create\fR() +fails if: +.TP 19n +[\fRZIP_ER_INVAL\fR] +\fIsrc\fR +is +\fRNULL\fR; +there is an integer overflow adding +\fIstart\fR +and +\fIlen\fR; +or +\fIlen\fR +is less than \-1. +.TP 19n +[\fRZIP_ER_MEMORY\fR] +Required memory could not be allocated. +.SH "SEE ALSO" +libzip(3), +zip_source(3) +zip_source(3) +.SH "HISTORY" +\fBzip_source_window_create\fR() +was added in libzip 1.8.0. +.SH "AUTHORS" +Dieter Baron <\fIdillo@nih.at\fR> +and +Thomas Klausner <\fItk@giga.or.at\fR> diff --git a/core/deps/libzip/man/zip_source_window_create.mdoc b/core/deps/libzip/man/zip_source_window_create.mdoc new file mode 100644 index 000000000..2d4f1d17e --- /dev/null +++ b/core/deps/libzip/man/zip_source_window_create.mdoc @@ -0,0 +1,99 @@ +.\" zip_source_window_create.mdoc -- create zip data source overlay +.\" Copyright (C) 2021 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd April 29, 2021 +.Dt ZIP_SOURCE_WINDOW_CREATE 3 +.Os +.Sh NAME +.Nm zip_source_window_create +.Nd create zip data source overlay +.Sh LIBRARY +libzip (-lzip) +.Sh SYNOPSIS +.In zip.h +.Ft zip_source_t * +.Fn zip_source_window_create "zip_source_t *source" "zip_uint64_t start" "zip_int64_t len" "zip_error_t *error" +.Sh DESCRIPTION +The +.Fn zip_source_window_create +function create a zip source from an underlying zip source, +restricting access to a particular window starting at byte +.Ar start +and having size +.Ar len . +If +.Ar len +is \-1, the window spans to the end of the underlying source. +.Pp +.Fn zip_source_window +and +.Fn zip_source_window_create +don't take ownership of +.Ar source . +The caller is responsible for freeing it. +(This is different to other layered sources.) +.Sh RETURN VALUES +Upon successful completion, the created source is returned. +Otherwise, +.Dv NULL +is returned and the error code in +.Ar error +is set to indicate the error. +.Sh ERRORS +.Fn zip_source_window_create +fails if: +.Bl -tag -width Er +.It Bq Er ZIP_ER_INVAL +.Ar src +is +.Dv NULL ; +there is an integer overflow adding +.Ar start +and +.Ar len ; +or +.Ar len +is less than \-1. +.It Bq Er ZIP_ER_MEMORY +Required memory could not be allocated. +.El +.Sh SEE ALSO +.Xr libzip 3 , +.Xr zip_source 3 +.Xr zip_source 3 +.Sh HISTORY +.Fn zip_source_window_create +was added in libzip 1.8.0. +.Sh AUTHORS +.An -nosplit +.An Dieter Baron Aq Mt dillo@nih.at +and +.An Thomas Klausner Aq Mt tk@giga.or.at diff --git a/core/deps/libzip/man/zip_source_write.html b/core/deps/libzip/man/zip_source_write.html index 88749bce8..94052b8be 100644 --- a/core/deps/libzip/man/zip_source_write.html +++ b/core/deps/libzip/man/zip_source_write.html @@ -5,7 +5,7 @@ Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_write.man b/core/deps/libzip/man/zip_source_write.man index 58d808ee2..4fb5cdb5d 100644 --- a/core/deps/libzip/man/zip_source_write.man +++ b/core/deps/libzip/man/zip_source_write.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_write.mdoc b/core/deps/libzip/man/zip_source_write.mdoc index 00836b034..fd033a85f 100644 --- a/core/deps/libzip/man/zip_source_write.mdoc +++ b/core/deps/libzip/man/zip_source_write.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2014-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_source_zip.html b/core/deps/libzip/man/zip_source_zip.html index ec8dd752e..8708e6d5e 100644 --- a/core/deps/libzip/man/zip_source_zip.html +++ b/core/deps/libzip/man/zip_source_zip.html @@ -2,10 +2,10 @@ + + + + ZIP_SOURCE_ZIP_FILE(3) + + + + + + + + +
ZIP_SOURCE_ZIP_FILE(3)Library Functions ManualZIP_SOURCE_ZIP_FILE(3)
+
+
+

+zip_source_zip_file, + zip_source_zip_file_create — +
create data source from zip file
+
+
+

+libzip (-lzip) +
+
+

+#include <zip.h> +

zip_source_t * +
+ zip_source_zip_file(zip_t + *archive, zip_t + *srcarchive, zip_uint64_t + srcidx, zip_flags_t + flags, zip_uint64_t + start, zip_int64_t + length, const char + *password);

+

zip_source_t * +
+ zip_source_zip_file_create(zip_t + *srcarchive, zip_uint64_t + srcidx, zip_flags_t + flags, zip_uint64_t + start, zip_int64_t + length, const char + *password, zip_error_t + *error);

+
+
+

+The functions zip_source_zip_file() and + zip_source_zip_file_create() create a zip source from + a file in a zip archive. The srcarchive argument is the + (open) zip archive containing the source zip file at index + srcidx. length bytes from offset + start will be used in the zip_source. If + length is -1, the rest of the file, starting from + start, is used. +

If you intend to copy a file from one archive to another, using + the flag ZIP_FL_COMPRESSED is more efficient, as it + avoids recompressing the file data.

+

Supported flags are:

+
+
+
Get the compressed data. This is only supported if the complete file data + is requested (start == 0 and + length == -1). This is not supported for changed + data. Default is uncompressed.
+
+
Get the encrypted data. (This flag implies + ZIP_FL_COMPRESSED.) This is only supported if the + complete file data is requested (start == 0 and + length == -1). Default is decrypted.
+
+
Try to get the original data without any changes that may have been made + to srcarchive after opening it.
+
+
+
+

+Upon successful completion, the created source is returned. Otherwise, + NULL is returned and the error code in + archive or error is set to + indicate the error. +
+
+

+zip_source_zip_file() and + zip_source_zip_file_create() fail if: +
+
[]
+
Unchanged data was requested, but it is not available.
+
[]
+
srcarchive, srcidx, + start, or length are + invalid.
+
[]
+
Required memory could not be allocated.
+
+Additionally, it can return all error codes from + zip_stat_index() and + zip_fopen_index(). +
+
+

+libzip(3), + zip_file_add(3), + zip_file_replace(3), + zip_source(3) +
+
+

+zip_source_zip_file() and + zip_source_zip_file_create() were added in libzip + 1.10.0. +
+
+

+Dieter Baron + <dillo@nih.at> and + Thomas Klausner + <tk@giga.or.at> +
+
+ + + + + +
March 10, 2023NiH
+ + diff --git a/core/deps/libzip/man/zip_source_zip_file.man b/core/deps/libzip/man/zip_source_zip_file.man new file mode 100644 index 000000000..ae7f16a19 --- /dev/null +++ b/core/deps/libzip/man/zip_source_zip_file.man @@ -0,0 +1,159 @@ +.\" Automatically generated from an mdoc input file. Do not edit. +.\" zip_source_zip_file.mdoc -- create data source from zip file +.\" Copyright (C) 2004-2021 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.TH "ZIP_SOURCE_ZIP_FILE" "3" "March 10, 2023" "NiH" "Library Functions Manual" +.nh +.if n .ad l +.SH "NAME" +\fBzip_source_zip_file\fR, +\fBzip_source_zip_file_create\fR +\- create data source from zip file +.SH "LIBRARY" +libzip (-lzip) +.SH "SYNOPSIS" +\fB#include \fR +.sp +\fIzip_source_t *\fR +.br +.PD 0 +.HP 4n +\fBzip_source_zip_file\fR(\fIzip_t\ *archive\fR, \fIzip_t\ *srcarchive\fR, \fIzip_uint64_t\ srcidx\fR, \fIzip_flags_t\ flags\fR, \fIzip_uint64_t\ start\fR, \fIzip_int64_t\ length\fR, \fIconst\ char\ *password\fR); +.PD +.PP +\fIzip_source_t *\fR +.br +.PD 0 +.HP 4n +\fBzip_source_zip_file_create\fR(\fIzip_t\ *srcarchive\fR, \fIzip_uint64_t\ srcidx\fR, \fIzip_flags_t\ flags\fR, \fIzip_uint64_t\ start\fR, \fIzip_int64_t\ length\fR, \fIconst\ char\ *password\fR, \fIzip_error_t\ *error\fR); +.PD +.SH "DESCRIPTION" +The functions +\fBzip_source_zip_file\fR() +and +\fBzip_source_zip_file_create\fR() +create a zip source from a file in a zip archive. +The +\fIsrcarchive\fR +argument is the (open) zip archive containing the source zip file +at index +\fIsrcidx\fR. +\fIlength\fR +bytes from offset +\fIstart\fR +will be used in the zip_source. +If +\fIlength\fR +is \-1, the rest of the file, starting from +\fIstart\fR, +is used. +.PP +If you intend to copy a file from one archive to another, using the flag +\fRZIP_FL_COMPRESSED\fR +is more efficient, as it avoids recompressing the file data. +.PP +Supported flags are: +.TP 22n +\fRZIP_FL_COMPRESSED\fR +Get the compressed data. +This is only supported if the complete file data is requested +(\fIstart\fR +== 0 and +\fIlength\fR +== \-1). +This is not supported for changed data. +Default is uncompressed. +.TP 22n +\fRZIP_FL_ENCRYPTED\fR +Get the encrypted data. +(This flag implies +\fRZIP_FL_COMPRESSED\fR.) +This is only supported if the complete file data is requested +(\fIstart\fR +== 0 and +\fIlength\fR +== \-1). +Default is decrypted. +.TP 22n +\fRZIP_FL_UNCHANGED\fR +Try to get the original data without any changes that may have been +made to +\fIsrcarchive\fR +after opening it. +.SH "RETURN VALUES" +Upon successful completion, the created source is returned. +Otherwise, +\fRNULL\fR +is returned and the error code in +\fIarchive\fR +or +\fIerror\fR +is set to indicate the error. +.SH "ERRORS" +\fBzip_source_zip_file\fR() +and +\fBzip_source_zip_file_create\fR() +fail if: +.TP 19n +[\fRZIP_ER_CHANGED\fR] +Unchanged data was requested, but it is not available. +.TP 19n +[\fRZIP_ER_INVAL\fR] +\fIsrcarchive\fR, +\fIsrcidx\fR, +\fIstart\fR, +or +\fIlength\fR +are invalid. +.TP 19n +[\fRZIP_ER_MEMORY\fR] +Required memory could not be allocated. +.PD 0 +.PP +Additionally, it can return all error codes from +\fBzip_stat_index\fR() +and +\fBzip_fopen_index\fR(). +.PD +.SH "SEE ALSO" +libzip(3), +zip_file_add(3), +zip_file_replace(3), +zip_source(3) +.SH "HISTORY" +\fBzip_source_zip_file\fR() +and +\fBzip_source_zip_file_create\fR() +were added in libzip 1.10.0. +.SH "AUTHORS" +Dieter Baron <\fIdillo@nih.at\fR> +and +Thomas Klausner <\fItk@giga.or.at\fR> diff --git a/core/deps/libzip/man/zip_source_zip_file.mdoc b/core/deps/libzip/man/zip_source_zip_file.mdoc new file mode 100644 index 000000000..a0842b749 --- /dev/null +++ b/core/deps/libzip/man/zip_source_zip_file.mdoc @@ -0,0 +1,144 @@ +.\" zip_source_zip_file.mdoc -- create data source from zip file +.\" Copyright (C) 2004-2021 Dieter Baron and Thomas Klausner +.\" +.\" This file is part of libzip, a library to manipulate ZIP archives. +.\" The authors can be contacted at +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in +.\" the documentation and/or other materials provided with the +.\" distribution. +.\" 3. The names of the authors may not be used to endorse or promote +.\" products derived from this software without specific prior +.\" written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS +.\" OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +.\" WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY +.\" DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +.\" GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +.\" INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +.\" IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +.\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +.\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +.\" +.Dd March 10, 2023 +.Dt ZIP_SOURCE_ZIP_FILE 3 +.Os +.Sh NAME +.Nm zip_source_zip_file , +.Nm zip_source_zip_file_create +.Nd create data source from zip file +.Sh LIBRARY +libzip (-lzip) +.Sh SYNOPSIS +.In zip.h +.Ft zip_source_t * +.Fn zip_source_zip_file "zip_t *archive" "zip_t *srcarchive" "zip_uint64_t srcidx" "zip_flags_t flags" "zip_uint64_t start" "zip_int64_t length" "const char *password" +.Ft zip_source_t * +.Fn zip_source_zip_file_create "zip_t *srcarchive" "zip_uint64_t srcidx" "zip_flags_t flags" "zip_uint64_t start" "zip_int64_t length" "const char *password" "zip_error_t *error" +.Sh DESCRIPTION +The functions +.Fn zip_source_zip_file +and +.Fn zip_source_zip_file_create +create a zip source from a file in a zip archive. +The +.Ar srcarchive +argument is the (open) zip archive containing the source zip file +at index +.Ar srcidx . +.Ar length +bytes from offset +.Ar start +will be used in the zip_source. +If +.Ar length +is \-1, the rest of the file, starting from +.Ar start , +is used. +.Pp +If you intend to copy a file from one archive to another, using the flag +.Dv ZIP_FL_COMPRESSED +is more efficient, as it avoids recompressing the file data. +.Pp +Supported flags are: +.Bl -tag -width 20n +.It Dv ZIP_FL_COMPRESSED +Get the compressed data. +This is only supported if the complete file data is requested +.Ar ( start +== 0 and +.Ar length +== \-1). +This is not supported for changed data. +Default is uncompressed. +.It Dv ZIP_FL_ENCRYPTED +Get the encrypted data. +(This flag implies +.Dv ZIP_FL_COMPRESSED . ) +This is only supported if the complete file data is requested +.Ar ( start +== 0 and +.Ar length +== \-1). +Default is decrypted. +.It Dv ZIP_FL_UNCHANGED +Try to get the original data without any changes that may have been +made to +.Ar srcarchive +after opening it. +.El +.Sh RETURN VALUES +Upon successful completion, the created source is returned. +Otherwise, +.Dv NULL +is returned and the error code in +.Ar archive +or +.Ar error +is set to indicate the error. +.Sh ERRORS +.Fn zip_source_zip_file +and +.Fn zip_source_zip_file_create +fail if: +.Bl -tag -width Er +.It Bq Er ZIP_ER_CHANGED +Unchanged data was requested, but it is not available. +.It Bq Er ZIP_ER_INVAL +.Ar srcarchive , +.Ar srcidx , +.Ar start , +or +.Ar length +are invalid. +.It Bq Er ZIP_ER_MEMORY +Required memory could not be allocated. +.El +Additionally, it can return all error codes from +.Fn zip_stat_index +and +.Fn zip_fopen_index . +.Sh SEE ALSO +.Xr libzip 3 , +.Xr zip_file_add 3 , +.Xr zip_file_replace 3 , +.Xr zip_source 3 +.Sh HISTORY +.Fn zip_source_zip_file +and +.Fn zip_source_zip_file_create +were added in libzip 1.10.0. +.Sh AUTHORS +.An -nosplit +.An Dieter Baron Aq Mt dillo@nih.at +and +.An Thomas Klausner Aq Mt tk@giga.or.at diff --git a/core/deps/libzip/man/zip_stat.html b/core/deps/libzip/man/zip_stat.html index 699c78f64..8cf0086ad 100644 --- a/core/deps/libzip/man/zip_stat.html +++ b/core/deps/libzip/man/zip_stat.html @@ -5,7 +5,7 @@ Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_stat.man b/core/deps/libzip/man/zip_stat.man index 93b6fdd5c..d7d4338f2 100644 --- a/core/deps/libzip/man/zip_stat.man +++ b/core/deps/libzip/man/zip_stat.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_stat.mdoc b/core/deps/libzip/man/zip_stat.mdoc index 4d5661ef4..9871770a3 100644 --- a/core/deps/libzip/man/zip_stat.mdoc +++ b/core/deps/libzip/man/zip_stat.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_stat_init.html b/core/deps/libzip/man/zip_stat_init.html index 99b36dee2..fd9c0f5a5 100644 --- a/core/deps/libzip/man/zip_stat_init.html +++ b/core/deps/libzip/man/zip_stat_init.html @@ -5,7 +5,7 @@ Copyright (C) 2006-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_stat_init.man b/core/deps/libzip/man/zip_stat_init.man index 75f3ea9b4..2ba5857f5 100644 --- a/core/deps/libzip/man/zip_stat_init.man +++ b/core/deps/libzip/man/zip_stat_init.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2006-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_stat_init.mdoc b/core/deps/libzip/man/zip_stat_init.mdoc index 0d464a614..bb5a0ffbe 100644 --- a/core/deps/libzip/man/zip_stat_init.mdoc +++ b/core/deps/libzip/man/zip_stat_init.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2006-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange.html b/core/deps/libzip/man/zip_unchange.html index 671abede5..ebf1113af 100644 --- a/core/deps/libzip/man/zip_unchange.html +++ b/core/deps/libzip/man/zip_unchange.html @@ -5,7 +5,7 @@ Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange.man b/core/deps/libzip/man/zip_unchange.man index c7ec25f55..30db8a9d9 100644 --- a/core/deps/libzip/man/zip_unchange.man +++ b/core/deps/libzip/man/zip_unchange.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange.mdoc b/core/deps/libzip/man/zip_unchange.mdoc index 6bde36bb5..3a2215588 100644 --- a/core/deps/libzip/man/zip_unchange.mdoc +++ b/core/deps/libzip/man/zip_unchange.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange_all.html b/core/deps/libzip/man/zip_unchange_all.html index ab94a0038..1c11f0baf 100644 --- a/core/deps/libzip/man/zip_unchange_all.html +++ b/core/deps/libzip/man/zip_unchange_all.html @@ -5,7 +5,7 @@ Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange_all.man b/core/deps/libzip/man/zip_unchange_all.man index 4fcbee320..f3aecea6f 100644 --- a/core/deps/libzip/man/zip_unchange_all.man +++ b/core/deps/libzip/man/zip_unchange_all.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange_all.mdoc b/core/deps/libzip/man/zip_unchange_all.mdoc index b676eb557..01806ecb0 100644 --- a/core/deps/libzip/man/zip_unchange_all.mdoc +++ b/core/deps/libzip/man/zip_unchange_all.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2003-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange_archive.html b/core/deps/libzip/man/zip_unchange_archive.html index f40ea4edc..35b842c34 100644 --- a/core/deps/libzip/man/zip_unchange_archive.html +++ b/core/deps/libzip/man/zip_unchange_archive.html @@ -5,7 +5,7 @@ Copyright (C) 2006-2017 Dieter Baron and Thomas Klausner This file is part of libzip, a library to manipulate ZIP archives. - The authors can be contacted at + The authors can be contacted at Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange_archive.man b/core/deps/libzip/man/zip_unchange_archive.man index 430eafc00..3f42fdc88 100644 --- a/core/deps/libzip/man/zip_unchange_archive.man +++ b/core/deps/libzip/man/zip_unchange_archive.man @@ -3,7 +3,7 @@ .\" Copyright (C) 2006-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zip_unchange_archive.mdoc b/core/deps/libzip/man/zip_unchange_archive.mdoc index b3b3d3f1a..4ec381154 100644 --- a/core/deps/libzip/man/zip_unchange_archive.mdoc +++ b/core/deps/libzip/man/zip_unchange_archive.mdoc @@ -2,7 +2,7 @@ .\" Copyright (C) 2006-2017 Dieter Baron and Thomas Klausner .\" .\" This file is part of libzip, a library to manipulate ZIP archives. -.\" The authors can be contacted at +.\" The authors can be contacted at .\" .\" Redistribution and use in source and binary forms, with or without .\" modification, are permitted provided that the following conditions diff --git a/core/deps/libzip/man/zipcmp.html b/core/deps/libzip/man/zipcmp.html index ddb397e3d..93654497d 100644 --- a/core/deps/libzip/man/zipcmp.html +++ b/core/deps/libzip/man/zipcmp.html @@ -2,10 +2,10 @@ -NA^g&%LyH*oj_^e^yGof22T^3?XLxn~ri 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

uuPa#jv*!k6D|8?-TjP{ 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

i+wgA1v&<_(%6V`je)Y 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

(IWK~-U4c^Om{ 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Ý()arzX(sz?eR3I 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

nv)GaS}ykl6{a&Tz~V*mVTW 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 + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include + +#include "zipint.h" + +bool +zip_secure_random(zip_uint8_t *buffer, zip_uint16_t length) { + memset(buffer, 0, length); + + return true; +} diff --git a/core/deps/libzip/regress/nonrandomopentest.c b/core/deps/libzip/regress/nonrandomopentest.c new file mode 100644 index 000000000..e4585a478 --- /dev/null +++ b/core/deps/libzip/regress/nonrandomopentest.c @@ -0,0 +1,57 @@ +/* + nonrandomopentest.c -- test nonrandomopen.so + Copyright (C) 2017-2021 Dieter Baron and Thomas Klausner + + This file is part of ckmame, a program to check rom sets for MAME. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The name of the author may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "zipint.h" + +#include +#include + +int +main(int argc, const char *argv[]) { + zip_uint8_t buf[1024]; + int i; + +#ifdef HAVE_CRYPTO + if (!zip_secure_random(buf, sizeof(buf))) { + fprintf(stderr, "zip_secure_random returned false\n"); + exit(1); + } + for (i = 0; i < sizeof(buf); i++) { + if (buf[i] != 0) { + fprintf(stderr, "non-zero byte found\n"); + exit(1); + } + } +#endif + exit(0); +} diff --git a/core/deps/libzip/regress/open_cons_extrabytes.test b/core/deps/libzip/regress/open_cons_extrabytes.test new file mode 100644 index 000000000..a50a490d7 --- /dev/null +++ b/core/deps/libzip/regress/open_cons_extrabytes.test @@ -0,0 +1,11 @@ +# zip_open: file has extra bytes at end of archive +program tryopen +file testextrabytes.zzip testextrabytes.zip +arguments -c testextrabytes.zzip +return 1 +stdout +opening 'testextrabytes.zzip' returned error 21/2 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_empty.test b/core/deps/libzip/regress/open_empty.test new file mode 100644 index 000000000..8c8ffdcfc --- /dev/null +++ b/core/deps/libzip/regress/open_empty.test @@ -0,0 +1,8 @@ +# zip_open: file contains no entry, but is valid +program tryopen +file testempty.zip testempty.zip +arguments testempty.zip +return 0 +stdout +opening 'testempty.zip' succeeded, 0 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_empty_2.test b/core/deps/libzip/regress/open_empty_2.test new file mode 100644 index 000000000..1c7b2ca81 --- /dev/null +++ b/core/deps/libzip/regress/open_empty_2.test @@ -0,0 +1,11 @@ +# zip_open: 0 size file is recognized as empty zip +program tryopen +file testfile.txt testfile.txt +arguments testfile.txt +return 1 +stdout +opening 'testfile.txt' returned error 19 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_extrabytes.test b/core/deps/libzip/regress/open_extrabytes.test new file mode 100644 index 000000000..c466216ad --- /dev/null +++ b/core/deps/libzip/regress/open_extrabytes.test @@ -0,0 +1,8 @@ +# zip_open: file has extra bytes at end of archive +program tryopen +file testextrabytes.zzip testextrabytes.zip +arguments testextrabytes.zzip +return 0 +stdout +opening 'testextrabytes.zzip' succeeded, 1 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_file_count.test b/core/deps/libzip/regress/open_file_count.test new file mode 100644 index 000000000..a99dbb7d7 --- /dev/null +++ b/core/deps/libzip/regress/open_file_count.test @@ -0,0 +1,15 @@ +# zip_open: various inconsistent files +program tryopen +file incons-file-count-high.zzip incons-file-count-high.zip +file incons-file-count-low.zzip incons-file-count-low.zip +file incons-file-count-overflow.zzip incons-file-count-overflow.zip +arguments incons-file-count-high.zzip incons-file-count-low.zzip incons-file-count-overflow.zzip +return 1 +stdout +opening 'incons-file-count-high.zzip' returned error 21/5 +opening 'incons-file-count-low.zzip' returned error 21/5 +opening 'incons-file-count-overflow.zzip' returned error 21/11 +end-of-inline-data +stderr +3 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_filename_duplicate.test b/core/deps/libzip/regress/open_filename_duplicate.test new file mode 100644 index 000000000..10536b029 --- /dev/null +++ b/core/deps/libzip/regress/open_filename_duplicate.test @@ -0,0 +1,8 @@ +# zip_open: file opens fine even though same file name appears twice +program tryopen +arguments filename_duplicate.zzip +return 0 +file filename_duplicate.zzip filename_duplicate.zip +stdout +opening 'filename_duplicate.zzip' succeeded, 2 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_filename_duplicate_consistency.test b/core/deps/libzip/regress/open_filename_duplicate_consistency.test new file mode 100644 index 000000000..acb152c3a --- /dev/null +++ b/core/deps/libzip/regress/open_filename_duplicate_consistency.test @@ -0,0 +1,11 @@ +# zip_open: file opens fine even though same file name appears twice +program tryopen +arguments -c filename_duplicate.zzip +return 1 +file filename_duplicate.zzip filename_duplicate.zip +stdout +opening 'filename_duplicate.zzip' returned error 10 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_filename_duplicate_empty.test b/core/deps/libzip/regress/open_filename_duplicate_empty.test new file mode 100644 index 000000000..a3306c3f1 --- /dev/null +++ b/core/deps/libzip/regress/open_filename_duplicate_empty.test @@ -0,0 +1,8 @@ +# zip_open: file opens fine even though same file name (empty file name) appears twice +program tryopen +arguments filename_duplicate_empty.zzip +return 0 +file filename_duplicate_empty.zzip filename_duplicate_empty.zip +stdout +opening 'filename_duplicate_empty.zzip' succeeded, 2 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_filename_duplicate_empty_consistency.test b/core/deps/libzip/regress/open_filename_duplicate_empty_consistency.test new file mode 100644 index 000000000..d1420f26d --- /dev/null +++ b/core/deps/libzip/regress/open_filename_duplicate_empty_consistency.test @@ -0,0 +1,11 @@ +# zip_open: file opens fine even though same file name (empty file name) appears twice +program tryopen +arguments -c filename_duplicate_empty.zzip +return 1 +file filename_duplicate_empty.zzip filename_duplicate_empty.zip +stdout +opening 'filename_duplicate_empty.zzip' returned error 10 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_filename_empty.test b/core/deps/libzip/regress/open_filename_empty.test new file mode 100644 index 000000000..4c45c21e6 --- /dev/null +++ b/core/deps/libzip/regress/open_filename_empty.test @@ -0,0 +1,8 @@ +# zip_open: file opens fine even though file name has length 0 +program tryopen +arguments filename_empty.zip +return 0 +file filename_empty.zip filename_empty.zip +stdout +opening 'filename_empty.zip' succeeded, 1 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_incons.test b/core/deps/libzip/regress/open_incons.test new file mode 100644 index 000000000..d2e51628a --- /dev/null +++ b/core/deps/libzip/regress/open_incons.test @@ -0,0 +1,77 @@ +# zip_open: various inconsistent files +program tryopen +file incons-archive-comment-longer.zzip incons-archive-comment-longer.zip +file incons-archive-comment-shorter.zzip incons-archive-comment-shorter.zip +file incons-cdoffset.zzip incons-cdoffset.zip +file incons-central-compression-method.zzip incons-central-compression-method.zip +file incons-central-compsize-larger.zzip incons-central-compsize-larger.zip +file incons-central-compsize-larger-toolarge.zzip incons-central-compsize-larger-toolarge.zip +file incons-central-compsize-smaller.zzip incons-central-compsize-smaller.zip +file incons-central-crc.zzip incons-central-crc.zip +file incons-central-date.zzip incons-central-date.zip +file incons-central-file-comment-longer.zzip incons-central-file-comment-longer.zip +file incons-central-file-comment-shorter.zzip incons-central-file-comment-shorter.zip +file incons-central-magic-bad.zzip incons-central-magic-bad.zip +file incons-central-magic-bad2.zzip incons-central-magic-bad2.zip +file incons-central-size-larger.zzip incons-central-size-larger.zip +file incons-data.zzip incons-data.zip +file incons-ef-central-size-wrong.zzip incons-ef-central-size-wrong.zip +file incons-ef-local-id-size.zzip incons-ef-local-id-size.zip +file incons-ef-local-id.zzip incons-ef-local-id.zip +file incons-ef-local-size.zzip incons-ef-local-size.zip +file incons-eocd-magic-bad.zzip incons-eocd-magic-bad.zip +file incons-file-count-high.zzip incons-file-count-high.zip +file incons-file-count-low.zzip incons-file-count-low.zip +file incons-file-count-overflow.zzip incons-file-count-overflow.zip +file incons-local-compression-method.zzip incons-local-compression-method.zip +file incons-local-compsize-larger.zzip incons-local-compsize-larger.zip +file incons-local-compsize-smaller.zzip incons-local-compsize-smaller.zip +file incons-local-crc.zzip incons-local-crc.zip +file incons-local-filename-long.zzip incons-local-filename-long.zip +file incons-local-filename-missing.zzip incons-local-filename-missing.zip +file incons-local-filename-short.zzip incons-local-filename-short.zip +file incons-local-filename.zzip incons-local-filename.zip +file incons-local-magic-bad.zzip incons-local-magic-bad.zip +file incons-local-size-larger.zzip incons-local-size-larger.zip +arguments -c incons-archive-comment-longer.zzip incons-archive-comment-shorter.zzip incons-cdoffset.zzip incons-central-compression-method.zzip incons-central-compsize-larger-toolarge.zzip incons-central-compsize-larger.zzip incons-central-compsize-smaller.zzip incons-central-crc.zzip incons-central-date.zzip incons-central-file-comment-longer.zzip incons-central-file-comment-shorter.zzip incons-central-magic-bad.zzip incons-central-magic-bad2.zzip incons-central-size-larger.zzip incons-data.zzip incons-ef-central-size-wrong.zzip incons-ef-local-id-size.zzip incons-ef-local-id.zzip incons-ef-local-size.zzip incons-eocd-magic-bad.zzip incons-file-count-high.zzip incons-file-count-low.zzip incons-file-count-overflow.zzip incons-local-compression-method.zzip incons-local-compsize-larger.zzip incons-local-compsize-smaller.zzip incons-local-crc.zzip incons-local-filename-long.zzip incons-local-filename-missing.zzip incons-local-filename-short.zzip incons-local-filename.zzip incons-local-magic-bad.zzip incons-local-size-larger.zzip +return 1 +# tryopen does not test checksums, so this is fine. +# different extra fields local vs. central is fine +stdout +opening 'incons-archive-comment-longer.zzip' returned error 21/2 +opening 'incons-archive-comment-shorter.zzip' returned error 21/2 +opening 'incons-cdoffset.zzip' returned error 21/1 +opening 'incons-central-compression-method.zzip' returned error 21/6 +opening 'incons-central-compsize-larger-toolarge.zzip' returned error 19 +opening 'incons-central-compsize-larger.zzip' returned error 21/6 +opening 'incons-central-compsize-smaller.zzip' returned error 21/6 +opening 'incons-central-crc.zzip' returned error 21/6 +opening 'incons-central-date.zzip' returned error 21/6 +opening 'incons-central-file-comment-longer.zzip' returned error 21/12 +opening 'incons-central-file-comment-shorter.zzip' returned error 21/260 +opening 'incons-central-magic-bad.zzip' returned error 19 +opening 'incons-central-magic-bad2.zzip' returned error 19 +opening 'incons-central-size-larger.zzip' returned error 21/6 +opening 'incons-data.zzip' succeeded, 1 entries +opening 'incons-ef-central-size-wrong.zzip' returned error 21/16 +opening 'incons-ef-local-id-size.zzip' returned error 21/16 +opening 'incons-ef-local-id.zzip' succeeded, 1 entries +opening 'incons-ef-local-size.zzip' returned error 21/16 +opening 'incons-eocd-magic-bad.zzip' returned error 19 +opening 'incons-file-count-high.zzip' returned error 21/5 +opening 'incons-file-count-low.zzip' returned error 21/5 +opening 'incons-file-count-overflow.zzip' returned error 21/11 +opening 'incons-local-compression-method.zzip' returned error 21/6 +opening 'incons-local-compsize-larger.zzip' returned error 21/6 +opening 'incons-local-compsize-smaller.zzip' returned error 21/6 +opening 'incons-local-crc.zzip' returned error 21/6 +opening 'incons-local-filename-long.zzip' returned error 17 +opening 'incons-local-filename-missing.zzip' returned error 21/6 +opening 'incons-local-filename-short.zzip' returned error 21/16 +opening 'incons-local-filename.zzip' returned error 21/6 +opening 'incons-local-magic-bad.zzip' returned error 19 +opening 'incons-local-size-larger.zzip' returned error 21/6 +end-of-inline-data +stderr +31 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_many_fail.test b/core/deps/libzip/regress/open_many_fail.test new file mode 100644 index 000000000..075587f37 --- /dev/null +++ b/core/deps/libzip/regress/open_many_fail.test @@ -0,0 +1,15 @@ +# zip_open: files with >65k that have issues +program tryopen +arguments manyfiles-zip64-modulo.zzip manyfiles-fewer.zzip manyfiles-more.zzip +return 1 +file manyfiles-zip64-modulo.zzip manyfiles-zip64-modulo.zip +file manyfiles-fewer.zzip manyfiles-fewer.zip +file manyfiles-more.zzip manyfiles-more.zip +stdout +opening 'manyfiles-zip64-modulo.zzip' returned error 21/5 +opening 'manyfiles-fewer.zzip' returned error 21/5 +opening 'manyfiles-more.zzip' returned error 21/5 +end-of-inline-data +stderr +3 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_many_ok.test b/core/deps/libzip/regress/open_many_ok.test new file mode 100644 index 000000000..4012c6741 --- /dev/null +++ b/core/deps/libzip/regress/open_many_ok.test @@ -0,0 +1,14 @@ +# zip_open: files open fine, have > 65k entries +program tryopen +arguments manyfiles.zip manyfiles-zip64.zip manyfiles-133000.zip manyfiles-65536.zip +return 0 +file manyfiles.zip manyfiles.zip +file manyfiles-zip64.zip manyfiles-zip64.zip +file manyfiles-133000.zip manyfiles-133000.zip +file manyfiles-65536.zip manyfiles-65536.zip +stdout +opening 'manyfiles.zip' succeeded, 70000 entries +opening 'manyfiles-zip64.zip' succeeded, 70000 entries +opening 'manyfiles-133000.zip' succeeded, 133000 entries +opening 'manyfiles-65536.zip' succeeded, 65536 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_multidisk.test b/core/deps/libzip/regress/open_multidisk.test new file mode 100644 index 000000000..2b569f7aa --- /dev/null +++ b/core/deps/libzip/regress/open_multidisk.test @@ -0,0 +1,11 @@ +# zip_open: file is part of a multi-disk zip archive +program tryopen +arguments test.piz +return 1 +file test.piz multidisk.zip +stdout +opening 'test.piz' returned error 1 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_new_but_exists.test b/core/deps/libzip/regress/open_new_but_exists.test new file mode 100644 index 000000000..26caa44ee --- /dev/null +++ b/core/deps/libzip/regress/open_new_but_exists.test @@ -0,0 +1,11 @@ +# zip_open: file shall be created but already exists +program tryopen +arguments -e test.zip +return 1 +file test.zip test.zip +stdout +opening 'test.zip' returned error 10 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_new_ok.test b/core/deps/libzip/regress/open_new_ok.test new file mode 100644 index 000000000..6dc063e68 --- /dev/null +++ b/core/deps/libzip/regress/open_new_ok.test @@ -0,0 +1,7 @@ +# zip_open: create new archive +program tryopen +arguments -n new.zip +return 0 +stdout +opening 'new.zip' succeeded, 0 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_nonarchive.test b/core/deps/libzip/regress/open_nonarchive.test new file mode 100644 index 000000000..1325734c8 --- /dev/null +++ b/core/deps/libzip/regress/open_nonarchive.test @@ -0,0 +1,11 @@ +# zip_open: file is not a zip archive +program tryopen +file CMakeLists.txt CMakeLists.txt +arguments CMakeLists.txt +return 1 +stdout +opening 'CMakeLists.txt' returned error 19 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_nosuchfile.test b/core/deps/libzip/regress/open_nosuchfile.test new file mode 100644 index 000000000..f82f702e9 --- /dev/null +++ b/core/deps/libzip/regress/open_nosuchfile.test @@ -0,0 +1,10 @@ +# zip_open: file doesn't exist +program tryopen +arguments nosuchfile +return 1 +stdout +opening 'nosuchfile' returned error 9 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_ok.test b/core/deps/libzip/regress/open_ok.test new file mode 100644 index 000000000..dbbc2c373 --- /dev/null +++ b/core/deps/libzip/regress/open_ok.test @@ -0,0 +1,8 @@ +# zip_open: file opens fine +program tryopen +arguments test.zip +return 0 +file test.zip test.zip +stdout +opening 'test.zip' succeeded, 3 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_too_short.test b/core/deps/libzip/regress/open_too_short.test new file mode 100644 index 000000000..f8e4685aa --- /dev/null +++ b/core/deps/libzip/regress/open_too_short.test @@ -0,0 +1,11 @@ +# zip_open: file is too short for even a central directory entry +program tryopen +arguments test.piz +return 1 +file test.piz bogus.zip +stdout +opening 'test.piz' returned error 19 +end-of-inline-data +stderr +1 errors +end-of-inline-data diff --git a/core/deps/libzip/regress/open_truncate.test b/core/deps/libzip/regress/open_truncate.test new file mode 100644 index 000000000..dc9e163f5 --- /dev/null +++ b/core/deps/libzip/regress/open_truncate.test @@ -0,0 +1,8 @@ +# zip_open: file opens fine and gets truncated +program tryopen +arguments -t test.zip +return 0 +file test.zip test.zip {} +stdout +opening 'test.zip' succeeded, 0 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_zip64_3mf.test b/core/deps/libzip/regress/open_zip64_3mf.test new file mode 100644 index 000000000..d8dee806a --- /dev/null +++ b/core/deps/libzip/regress/open_zip64_3mf.test @@ -0,0 +1,8 @@ +# zip_open: ZIP64 file opens fine even when most eocd entries are 0xff (3MF format) +program tryopen +arguments test.zip +return 0 +file test.zip zip64-3mf.zip +stdout +opening 'test.zip' succeeded, 1 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/open_zip64_ok.test b/core/deps/libzip/regress/open_zip64_ok.test new file mode 100644 index 000000000..945b5f805 --- /dev/null +++ b/core/deps/libzip/regress/open_zip64_ok.test @@ -0,0 +1,8 @@ +# zip_open: ZIP64 file opens fine +program tryopen +arguments test.zip +return 0 +file test.zip zip64.zip +stdout +opening 'test.zip' succeeded, 1 entries +end-of-inline-data diff --git a/core/deps/libzip/regress/ossfuzz.sh b/core/deps/libzip/regress/ossfuzz.sh new file mode 100644 index 000000000..01e41708c --- /dev/null +++ b/core/deps/libzip/regress/ossfuzz.sh @@ -0,0 +1,35 @@ +#!/bin/bash -eu +# Copyright 2019 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ + +# This script is meant to be run by +# https://github.com/google/oss-fuzz/blob/master/projects/libzip/Dockerfile + +mkdir build +cd build +cmake -DBUILD_SHARED_LIBS=OFF -DENABLE_GNUTLS=OFF -DENABLE_MBEDTLS=OFF -DENABLE_OPENSSL=OFF -DBUILD_TOOLS=OFF -DENABLE_LZMA=OFF .. +make -j$(nproc) + +$CXX $CXXFLAGS -std=c++11 -I. -I../lib \ + $SRC/libzip/regress/zip_read_fuzzer.cc \ + -o $OUT/zip_read_fuzzer \ + $LIB_FUZZING_ENGINE $SRC/libzip/build/lib/libzip.a -lz + +find $SRC/libzip/regress -name "*.zip" | \ + xargs zip $OUT/zip_read_fuzzer_seed_corpus.zip + +cp $SRC/libzip/regress/zip_read_fuzzer.dict $OUT/ + diff --git a/core/deps/libzip/regress/preload.test b/core/deps/libzip/regress/preload.test new file mode 100644 index 000000000..5a62114c1 --- /dev/null +++ b/core/deps/libzip/regress/preload.test @@ -0,0 +1,4 @@ +description test if preload works +program nonrandomopentest +return 0 +preload nonrandomopen.so diff --git a/core/deps/libzip/regress/progress.test b/core/deps/libzip/regress/progress.test new file mode 100644 index 000000000..e5c26003c --- /dev/null +++ b/core/deps/libzip/regress/progress.test @@ -0,0 +1,12 @@ +# test default compression stores if smaller; print progress +return 0 +arguments -n -- test.zip print_progress add compressible aaaaaaaaaaaaaa add uncompressible uncompressible add_nul large-compressible 8200 add_file large-uncompressible large-uncompressible 0 -1 +file test.zip {} cm-default.zip +file large-uncompressible large-uncompressible +stdout +0.0% done +25.0% done +50.0% done +75.0% done +100.0% done +end-of-inline-data diff --git a/core/deps/libzip/regress/read_seek_read.test b/core/deps/libzip/regress/read_seek_read.test new file mode 100644 index 000000000..b743f9fad --- /dev/null +++ b/core/deps/libzip/regress/read_seek_read.test @@ -0,0 +1,9 @@ +# read past EOF, seek to beginning, read again +return 0 +arguments test.zip fopen test fread 0 10 fseek 0 0 set fread 0 5 +file test.zip test.zip +stdout +opened 'test' as file 0 +test +test +end-of-inline-data diff --git a/core/deps/libzip/regress/rename_ascii.test b/core/deps/libzip/regress/rename_ascii.test new file mode 100644 index 000000000..435ce5ee6 --- /dev/null +++ b/core/deps/libzip/regress/rename_ascii.test @@ -0,0 +1,4 @@ +# rename file to ASCII name in zip archive +return 0 +arguments testfile rename 0 testfile.txt +file testfile testfile-UTF8.zip testfile.zip diff --git a/core/deps/libzip/regress/rename_cp437.test b/core/deps/libzip/regress/rename_cp437.test new file mode 100644 index 000000000..5e80adadd --- /dev/null +++ b/core/deps/libzip/regress/rename_cp437.test @@ -0,0 +1,4 @@ +# rename file to CP437 name in zip archive (fails) +return 0 +arguments -x testfile.zip rename 0 "8182838485868788898A8B8C8D8E8F90" +file testfile.zip testfile.zip testfile-cp437.zip diff --git a/core/deps/libzip/regress/rename_deleted.test b/core/deps/libzip/regress/rename_deleted.test new file mode 100644 index 000000000..29c99e976 --- /dev/null +++ b/core/deps/libzip/regress/rename_deleted.test @@ -0,0 +1,7 @@ +# rename deleted entry in zip archive (fails) +return 1 +arguments testfile.zip delete 1 delete 3 rename 1 othername +file testfile.zip testcomment.zip testcomment13.zip +stderr +can't rename file at index '1' to 'othername': Entry has been deleted +end-of-inline-data diff --git a/core/deps/libzip/regress/rename_fail.test b/core/deps/libzip/regress/rename_fail.test new file mode 100644 index 000000000..d0537346c --- /dev/null +++ b/core/deps/libzip/regress/rename_fail.test @@ -0,0 +1,7 @@ +# rename file inside zip archive, but file name already exists +return 1 +arguments rename.zip rename 0 file4 +file rename.zip testcomment.zip +stderr +can't rename file at index '0' to 'file4': File already exists +end-of-inline-data diff --git a/core/deps/libzip/regress/rename_ok.test b/core/deps/libzip/regress/rename_ok.test new file mode 100644 index 000000000..22292d0e8 --- /dev/null +++ b/core/deps/libzip/regress/rename_ok.test @@ -0,0 +1,4 @@ +# rename file inside zip archive +return 0 +arguments rename.zip rename 1 notfile2 +file rename.zip testcomment.zip rename_ok.zip diff --git a/core/deps/libzip/regress/rename_ok.zip b/core/deps/libzip/regress/rename_ok.zip new file mode 100644 index 0000000000000000000000000000000000000000..ad073060c75447df53e4872d172ce8b8018c7491 GIT binary patch literal 709 zcmZ{iy-ve07)8I529-z|5wLj$q==s`Obq-itx%*k4-lmeiPYLCF{q4;-H=$AkdVro z@BlEfGcv-?wUe|!N}L;6NqqeI*nY=qI;5|&r%8L~?c#bJeI1)tDNcucH|VdBbr&2Q z>jdK_4bYCHP+X{-$(Y4ymMc7m+;jXH&j;`2_4#%KES(m3LMr{+obCZ>V~;4$6m%)YNBmp-V@|d kPm(j-D&Z{5q9ncI)dE;7GxOe@FV}IMt`z59)!`Ys-)p9&X8-^I literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/rename_utf8.test b/core/deps/libzip/regress/rename_utf8.test new file mode 100644 index 000000000..d4b13a56b --- /dev/null +++ b/core/deps/libzip/regress/rename_utf8.test @@ -0,0 +1,7 @@ +# rename file to UTF-8 name in zip archive +return 0 +arguments -i testfile dummy +stdin +rename 0 ÄÖÜßäöü +end-of-inline-data +file testfile testfile.zip testfile-UTF8.zip diff --git a/core/deps/libzip/regress/rename_utf8_encmismatch.test b/core/deps/libzip/regress/rename_utf8_encmismatch.test new file mode 100644 index 000000000..e20dbc926 --- /dev/null +++ b/core/deps/libzip/regress/rename_utf8_encmismatch.test @@ -0,0 +1,7 @@ +# rename file to UTF-8 name in zip archive with CP437 comment (sets InfoZIP UTF-8 Name Extension) +return 0 +arguments -i testfile dummy +stdin +rename 0 ÄÖÜßäöü +end-of-inline-data +file testfile test-cp437-fc.zip test-cp437-fc-utf-8-filename.zip diff --git a/core/deps/libzip/regress/reopen.test b/core/deps/libzip/regress/reopen.test new file mode 100644 index 000000000..7e41bbc30 --- /dev/null +++ b/core/deps/libzip/regress/reopen.test @@ -0,0 +1,9 @@ +description check the reopen functionality +return 0 +arguments -- testbuffer.zip cat 0 replace_file_contents 0 "Overwritten\n" cat 0 add newfile.txt "A new file\n" cat 1 +file testbuffer.zip testbuffer.zip testbuffer_reopen.zip +stdout +This is a test, and it seems to have been successful. +Overwritten +A new file +end-of-inline-data diff --git a/core/deps/libzip/regress/reopen_partial.test b/core/deps/libzip/regress/reopen_partial.test new file mode 100644 index 000000000..a963ad053 --- /dev/null +++ b/core/deps/libzip/regress/reopen_partial.test @@ -0,0 +1,8 @@ +description check the reopen functionality (partial reads) +return 0 +arguments -- testbuffer.zip cat 0 replace_file_contents 0 "Overwritten\n" cat_partial 0 4 5 add newfile.txt "A new file\n" cat_partial 1 2 3 +file testbuffer.zip testbuffer.zip testbuffer_reopen.zip +stdout +This is a test, and it seems to have been successful. +writtnew +end-of-inline-data diff --git a/core/deps/libzip/regress/reopen_partial_rest.test b/core/deps/libzip/regress/reopen_partial_rest.test new file mode 100644 index 000000000..ac844e341 --- /dev/null +++ b/core/deps/libzip/regress/reopen_partial_rest.test @@ -0,0 +1,9 @@ +description check the reopen functionality (partial reads with -1 length) +return 0 +arguments -- testbuffer.zip cat 0 replace_file_contents 0 "Overwritten\n" cat_partial 0 4 -1 add newfile.txt "A new file\n" cat_partial 1 2 -1 +file testbuffer.zip testbuffer.zip testbuffer_reopen.zip +stdout +This is a test, and it seems to have been successful. +written +new file +end-of-inline-data diff --git a/core/deps/libzip/regress/runtest.in b/core/deps/libzip/regress/runtest.in deleted file mode 100644 index a6eb1407a..000000000 --- a/core/deps/libzip/regress/runtest.in +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env perl - -# runtest -- run regression tests -# Copyright (C) 2002-2014 Dieter Baron and Thomas Klausner -# -# This file is part of ckmame, a program to check rom sets for MAME. -# The authors can be contacted at -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# 3. The names of the authors may not be used to endorse or promote -# products derived from this software without specific prior -# written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS -# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY -# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -# GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER -# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR -# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN -# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -use strict; - -BEGIN { push @INC, '@abs_srcdir@'; } - -use NiHTest; - -my $test = NiHTest::new({ default_program => 'ziptool_regress', srcdir => '@srcdir@', top_builddir => '@top_builddir@', zipcmp => '../../src/zipcmp', zipcmp_flags => '-p' }); - -sub mangle_test { - my ($test, $variant) = @_; - - if (defined($test->{test}->{preload})) { - if (!defined($test->{test}->{features})) { - $test->{test}->{features} = []; - } - push @{$test->{test}->{features}}, 'SHARED'; - } - - return 1; -} - - -$test->add_comparator('zip/zip', \&NiHTest::comparator_zip); -$test->add_hook('post_parse', \&mangle_test); - -$test->run(@ARGV); diff --git a/core/deps/libzip/regress/set_comment_all.test b/core/deps/libzip/regress/set_comment_all.test new file mode 100644 index 000000000..4107d8807 --- /dev/null +++ b/core/deps/libzip/regress/set_comment_all.test @@ -0,0 +1,4 @@ +# change local and global comments in a zip archive +return 0 +arguments testcomment.zip set_archive_comment "This is the new,\r\nmultiline archive comment.\r\nAin't it nice?" set_file_comment 0 "File comment no 0" set_file_comment 1 "File comment no 1" set_file_comment 2 "File comment no 2" set_file_comment 3 "File comment no 3" +file testcomment.zip testcomment.zip testchanged.zip diff --git a/core/deps/libzip/regress/set_comment_localonly.test b/core/deps/libzip/regress/set_comment_localonly.test new file mode 100644 index 000000000..66b9b8d4f --- /dev/null +++ b/core/deps/libzip/regress/set_comment_localonly.test @@ -0,0 +1,4 @@ +# change file comments in a zip archive +return 0 +arguments testcomment.zip set_file_comment 0 "File comment no 0" set_file_comment 1 "File comment no 1" set_file_comment 3 "File comment no 3" set_file_comment 2 "" +file testcomment.zip testcomment.zip testchangedlocal.zip diff --git a/core/deps/libzip/regress/set_comment_removeglobal.test b/core/deps/libzip/regress/set_comment_removeglobal.test new file mode 100644 index 000000000..3b4893361 --- /dev/null +++ b/core/deps/libzip/regress/set_comment_removeglobal.test @@ -0,0 +1,4 @@ +# remove archive comment +return 0 +arguments testcomment.zip set_archive_comment "" +file testcomment.zip testcomment.zip testcommentremoved.zip diff --git a/core/deps/libzip/regress/set_comment_revert.test b/core/deps/libzip/regress/set_comment_revert.test new file mode 100644 index 000000000..3abbbb1ea --- /dev/null +++ b/core/deps/libzip/regress/set_comment_revert.test @@ -0,0 +1,4 @@ +# start changing local and global comments, but revert before closing +return 0 +arguments testcomment.zip set_archive_comment "some long string, a bit longer than this at least" set_file_comment 0 "File comment no 0" set_file_comment 1 "File comment no 1" set_file_comment 3 "File comment no 3" set_file_comment 2 "" unchange_all +file testcomment.zip testcomment.zip diff --git a/core/deps/libzip/regress/set_compression_bzip2_to_deflate.test b/core/deps/libzip/regress/set_compression_bzip2_to_deflate.test new file mode 100644 index 000000000..bbc09c2e9 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_bzip2_to_deflate.test @@ -0,0 +1,5 @@ +# change method from bzip2 to deflated +features HAVE_LIBBZ2 +return 0 +arguments test.zip set_file_compression 0 deflate 0 +file test.zip testbzip2.zip testdeflated.zip diff --git a/core/deps/libzip/regress/set_compression_deflate_to_bzip2.test b/core/deps/libzip/regress/set_compression_deflate_to_bzip2.test new file mode 100644 index 000000000..e56e4570a --- /dev/null +++ b/core/deps/libzip/regress/set_compression_deflate_to_bzip2.test @@ -0,0 +1,5 @@ +# change method from deflated to bzip2 +features HAVE_LIBBZ2 +return 0 +arguments test.zip set_file_compression 0 bzip2 0 +file test.zip testdeflated.zip testbzip2.zip diff --git a/core/deps/libzip/regress/set_compression_deflate_to_deflate.test b/core/deps/libzip/regress/set_compression_deflate_to_deflate.test new file mode 100644 index 000000000..99ee0d746 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_deflate_to_deflate.test @@ -0,0 +1,4 @@ +# change method from deflated to deflated (no change) +return 0 +arguments test.zip set_file_compression 0 deflate 0 +file test.zip testdeflated.zip diff --git a/core/deps/libzip/regress/set_compression_deflate_to_store.test b/core/deps/libzip/regress/set_compression_deflate_to_store.test new file mode 100644 index 000000000..ded1a8579 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_deflate_to_store.test @@ -0,0 +1,4 @@ +# change method from deflated to stored +return 0 +arguments test.zip set_file_compression 0 store 0 +file test.zip testdeflated.zip teststored.zip diff --git a/core/deps/libzip/regress/set_compression_lzma_no_eos_to_store.test b/core/deps/libzip/regress/set_compression_lzma_no_eos_to_store.test new file mode 100644 index 000000000..48ae8a963 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_lzma_no_eos_to_store.test @@ -0,0 +1,5 @@ +# change method from lzma-compressed (id 14) without EOS/EOPM marker to stored +features HAVE_LIBLZMA +return 0 +arguments test.zip set_file_compression 0 store 0 +file test.zip lzma-no-eos.zip stored-no-eos.zip diff --git a/core/deps/libzip/regress/set_compression_lzma_to_store.test b/core/deps/libzip/regress/set_compression_lzma_to_store.test new file mode 100644 index 000000000..7c85bcc35 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_lzma_to_store.test @@ -0,0 +1,5 @@ +# change method from lzma-compressed (id 14) to stored +features HAVE_LIBLZMA +return 0 +arguments test.zip set_file_compression 0 store 0 +file test.zip testfile-lzma.zip testfile-stored-dos.zip diff --git a/core/deps/libzip/regress/set_compression_store_to_bzip2.test b/core/deps/libzip/regress/set_compression_store_to_bzip2.test new file mode 100644 index 000000000..5a7a0f46a --- /dev/null +++ b/core/deps/libzip/regress/set_compression_store_to_bzip2.test @@ -0,0 +1,5 @@ +# change method from stored to bzip2 +features HAVE_LIBBZ2 +return 0 +arguments test.zip set_file_compression 0 bzip2 0 +file test.zip teststored.zip testbzip2.zip diff --git a/core/deps/libzip/regress/set_compression_store_to_deflate.test b/core/deps/libzip/regress/set_compression_store_to_deflate.test new file mode 100644 index 000000000..3467ad6b5 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_store_to_deflate.test @@ -0,0 +1,4 @@ +# change method from stored to deflated +return 0 +arguments test.zip set_file_compression 0 deflate 0 +file test.zip teststored.zip testdeflated.zip diff --git a/core/deps/libzip/regress/set_compression_store_to_lzma.test b/core/deps/libzip/regress/set_compression_store_to_lzma.test new file mode 100644 index 000000000..239e68b6d --- /dev/null +++ b/core/deps/libzip/regress/set_compression_store_to_lzma.test @@ -0,0 +1,5 @@ +# change method from stored to lzma-compressed (Id 14) +features HAVE_LIBLZMA +return 0 +arguments test.zip set_file_compression 0 lzma 0 +file test.zip testfile-stored-dos.zip testfile-lzma.zip diff --git a/core/deps/libzip/regress/set_compression_store_to_store.test b/core/deps/libzip/regress/set_compression_store_to_store.test new file mode 100644 index 000000000..486274e7c --- /dev/null +++ b/core/deps/libzip/regress/set_compression_store_to_store.test @@ -0,0 +1,4 @@ +# change method from stored to stored (no change) +return 0 +arguments test.zip set_file_compression 0 store 0 +file test.zip teststored.zip diff --git a/core/deps/libzip/regress/set_compression_store_to_xz.test b/core/deps/libzip/regress/set_compression_store_to_xz.test new file mode 100644 index 000000000..4838f0516 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_store_to_xz.test @@ -0,0 +1,5 @@ +# change method from stored to xz-compressed +features HAVE_LIBLZMA +return 0 +arguments test.zip set_file_compression 0 xz 0 +file test.zip testfile-stored-dos.zip testfile-xz.zip diff --git a/core/deps/libzip/regress/set_compression_store_to_zstd.test b/core/deps/libzip/regress/set_compression_store_to_zstd.test new file mode 100644 index 000000000..eb734cf4a --- /dev/null +++ b/core/deps/libzip/regress/set_compression_store_to_zstd.test @@ -0,0 +1,5 @@ +# change method from stored to zstd-compressed +features HAVE_LIBZSTD +return 0 +arguments test.zip set_file_compression 0 zstd 0 +file test.zip testfile-stored-dos.zip testfile-zstd.zip diff --git a/core/deps/libzip/regress/set_compression_unknown.test b/core/deps/libzip/regress/set_compression_unknown.test new file mode 100644 index 000000000..1f7defdf1 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_unknown.test @@ -0,0 +1,7 @@ +# change method to unknown +return 1 +arguments test.zip set_file_compression 0 unknown 0 +file test.zip teststored.zip +stderr +can't set file compression method at index '0' to 'unknown', flags '0': Compression method not supported +end-of-inline-data diff --git a/core/deps/libzip/regress/set_compression_xz_to_store.test b/core/deps/libzip/regress/set_compression_xz_to_store.test new file mode 100644 index 000000000..88fdf1010 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_xz_to_store.test @@ -0,0 +1,5 @@ +# change method from xz-compressed to stored +features HAVE_LIBLZMA +return 0 +arguments test.zip set_file_compression 0 store 0 +file test.zip testfile-xz.zip testfile-stored-dos.zip diff --git a/core/deps/libzip/regress/set_compression_zstd_to_store.test b/core/deps/libzip/regress/set_compression_zstd_to_store.test new file mode 100644 index 000000000..d652cef36 --- /dev/null +++ b/core/deps/libzip/regress/set_compression_zstd_to_store.test @@ -0,0 +1,5 @@ +# change method from zstd-compressed to stored +features HAVE_LIBZSTD +return 0 +arguments test.zip set_file_compression 0 store 0 +file test.zip testfile-zstd.zip testfile-stored-dos.zip diff --git a/core/deps/libzip/regress/set_file_dostime.test b/core/deps/libzip/regress/set_file_dostime.test new file mode 100644 index 000000000..d0bf6c953 --- /dev/null +++ b/core/deps/libzip/regress/set_file_dostime.test @@ -0,0 +1,4 @@ +# change dostime in a zip archive (use torrentzip default time) +return 0 +arguments testfile set_file_dostime 0 48128 8600 +file testfile testfile.zip testfile0.zip diff --git a/core/deps/libzip/regress/set_file_mtime.test b/core/deps/libzip/regress/set_file_mtime.test new file mode 100644 index 000000000..0919342c2 --- /dev/null +++ b/core/deps/libzip/regress/set_file_mtime.test @@ -0,0 +1,4 @@ +# change mtime in a zip archive +return 0 +arguments testfile set_file_mtime 0 1407272201 +file testfile testfile.zip testfile2014.zip diff --git a/core/deps/libzip/regress/set_file_mtime_pkware.test b/core/deps/libzip/regress/set_file_mtime_pkware.test new file mode 100644 index 000000000..1c07b7b75 --- /dev/null +++ b/core/deps/libzip/regress/set_file_mtime_pkware.test @@ -0,0 +1,7 @@ +# change mtime in a zip archive, fails because file is PKWare-encrypted +return 1 +arguments testfile set_file_mtime 0 1407272201 +file testfile encrypt.zip +stderr +can't set file mtime at index '0' to '1407272201': Operation not supported +end-of-inline-data diff --git a/core/deps/libzip/regress/short b/core/deps/libzip/regress/short new file mode 100644 index 000000000..eba61a783 --- /dev/null +++ b/core/deps/libzip/regress/short @@ -0,0 +1 @@ +short \ No newline at end of file diff --git a/core/deps/libzip/regress/source_hole.c b/core/deps/libzip/regress/source_hole.c new file mode 100644 index 000000000..bd15d179c --- /dev/null +++ b/core/deps/libzip/regress/source_hole.c @@ -0,0 +1,577 @@ +/* + source_hole.c -- source for handling huge files that are mostly NULs + Copyright (C) 2014-2021 Dieter Baron and Thomas Klausner + + This file is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "zip.h" + +/* public API */ + +zip_source_t *source_hole_create(const char *, int flags, zip_error_t *); + + +#ifndef EFTYPE +#define EFTYPE EINVAL +#endif + + +#define MY_MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define FRAGMENT_SIZE (8 * 1024) + +#define MARK_BEGIN "NiH0" +#define MARK_DATA "NiH1" +#define MARK_NUL "NiH2" + + +typedef struct buffer { + zip_uint64_t fragment_size; + zip_uint8_t **fragment; + zip_uint64_t nfragments; + zip_uint64_t size; + zip_uint64_t offset; +} buffer_t; + +static void buffer_free(buffer_t *buffer); +static buffer_t *buffer_from_file(const char *fname, int flags, zip_error_t *error); +static buffer_t *buffer_new(void); +static zip_int64_t buffer_read(buffer_t *buffer, zip_uint8_t *data, zip_uint64_t length, zip_error_t *error); +static int buffer_read_file(buffer_t *buffer, FILE *f, zip_error_t *error); +static zip_int64_t buffer_seek(buffer_t *buffer, void *data, zip_uint64_t length, zip_error_t *error); +static int buffer_to_file(buffer_t *buffer, const char *fname, zip_error_t *error); +static zip_int64_t buffer_write(buffer_t *buffer, const zip_uint8_t *data, zip_uint64_t length, zip_error_t *error); +static zip_uint64_t get_u64(const zip_uint8_t *b); +static int only_nul(const zip_uint8_t *data, zip_uint64_t length); +static int write_nuls(zip_uint64_t n, FILE *f); +static int write_u64(zip_uint64_t u64, FILE *f); + + +typedef struct hole { + zip_error_t error; + char *fname; + buffer_t *in; + buffer_t *out; +} hole_t; + +static hole_t *hole_new(const char *fname, int flags, zip_error_t *error); +static zip_int64_t source_hole_cb(void *ud, void *data, zip_uint64_t length, zip_source_cmd_t command); + + +zip_source_t * +source_hole_create(const char *fname, int flags, zip_error_t *error) { + hole_t *ud = hole_new(fname, flags, error); + + if (ud == NULL) { + return NULL; + } + return zip_source_function_create(source_hole_cb, ud, error); +} + + +static void +buffer_free(buffer_t *buffer) { + zip_uint64_t i; + + if (buffer == NULL) { + return; + } + + if (buffer->fragment) { + for (i = 0; i < buffer->nfragments; i++) { + free(buffer->fragment[i]); + } + free(buffer->fragment); + } + free(buffer); +} + + +static buffer_t * +buffer_from_file(const char *fname, int flags, zip_error_t *error) { + buffer_t *buffer; + FILE *f; + + if ((buffer = buffer_new()) == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + if ((flags & ZIP_TRUNCATE) == 0) { + if ((f = fopen(fname, "rb")) == NULL) { + if (!(errno == ENOENT && (flags & ZIP_CREATE))) { + buffer_free(buffer); + return NULL; + } + } + else { + if (buffer_read_file(buffer, f, error) < 0) { + buffer_free(buffer); + fclose(f); + return NULL; + } + fclose(f); + } + } + + return buffer; +} + + +static buffer_t * +buffer_new(void) { + buffer_t *buffer; + + if ((buffer = (buffer_t *)malloc(sizeof(*buffer))) == NULL) { + return NULL; + } + + buffer->fragment = NULL; + buffer->nfragments = 0; + buffer->fragment_size = FRAGMENT_SIZE; + buffer->size = 0; + buffer->offset = 0; + + return buffer; +} + + +static zip_int64_t +buffer_read(buffer_t *buffer, zip_uint8_t *data, zip_uint64_t length, zip_error_t *error) { + zip_uint64_t n, i, fragment_offset; + + length = MY_MIN(length, buffer->size - buffer->offset); + + if (length == 0) { + return 0; + } + if (length > ZIP_INT64_MAX) { + return -1; + } + + i = buffer->offset / buffer->fragment_size; + fragment_offset = buffer->offset % buffer->fragment_size; + n = 0; + while (n < length) { + zip_uint64_t left = MY_MIN(length - n, buffer->fragment_size - fragment_offset); + + if (buffer->fragment[i]) { + memcpy(data + n, buffer->fragment[i] + fragment_offset, left); + } + else { + memset(data + n, 0, left); + } + + n += left; + i++; + fragment_offset = 0; + } + + buffer->offset += n; + return (zip_int64_t)n; +} + + +static int +buffer_read_file(buffer_t *buffer, FILE *f, zip_error_t *error) { + zip_uint8_t b[20]; + zip_uint64_t i; + + if (fread(b, 20, 1, f) != 1) { + zip_error_set(error, ZIP_ER_READ, errno); + return -1; + } + + if (memcmp(b, MARK_BEGIN, 4) != 0) { + zip_error_set(error, ZIP_ER_READ, EFTYPE); + return -1; + } + + buffer->fragment_size = get_u64(b + 4); + buffer->size = get_u64(b + 12); + + if (buffer->fragment_size == 0) { + zip_error_set(error, ZIP_ER_INCONS, 0); + return -1; + } + + buffer->nfragments = buffer->size / buffer->fragment_size; + if (buffer->size % buffer->fragment_size != 0) { + buffer->nfragments += 1; + } + + if ((buffer->nfragments > SIZE_MAX / sizeof(buffer->fragment[0])) || ((buffer->fragment = (zip_uint8_t **)malloc(sizeof(buffer->fragment[0]) * buffer->nfragments)) == NULL)) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return -1; + } + + for (i = 0; i < buffer->nfragments; i++) { + buffer->fragment[i] = NULL; + } + + i = 0; + while (i < buffer->nfragments) { + if (fread(b, 4, 1, f) != 1) { + zip_error_set(error, ZIP_ER_READ, errno); + return -1; + } + + if (memcmp(b, MARK_DATA, 4) == 0) { + if (buffer->fragment_size > SIZE_MAX) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return -1; + } + if ((buffer->fragment[i] = (zip_uint8_t *)malloc(buffer->fragment_size)) == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return -1; + } + if (fread(buffer->fragment[i], buffer->fragment_size, 1, f) != 1) { + zip_error_set(error, ZIP_ER_READ, errno); + return -1; + } + i++; + } + else if (memcmp(b, MARK_NUL, 4) == 0) { + if (fread(b, 8, 1, f) != 1) { + zip_error_set(error, ZIP_ER_READ, errno); + return -1; + } + i += get_u64(b); + } + else { + zip_error_set(error, ZIP_ER_READ, EFTYPE); + return -1; + } + } + + return 0; +} + +static zip_int64_t +buffer_seek(buffer_t *buffer, void *data, zip_uint64_t length, zip_error_t *error) { + zip_int64_t new_offset = zip_source_seek_compute_offset(buffer->offset, buffer->size, data, length, error); + + if (new_offset < 0) { + return -1; + } + + buffer->offset = (zip_uint64_t)new_offset; + return 0; +} + + +static int +buffer_to_file(buffer_t *buffer, const char *fname, zip_error_t *error) { + FILE *f = fopen(fname, "wb"); + zip_uint64_t i; + zip_uint64_t nul_run; + + if (f == NULL) { + zip_error_set(error, ZIP_ER_OPEN, errno); + return -1; + } + + fwrite(MARK_BEGIN, 4, 1, f); + write_u64(buffer->fragment_size, f); + write_u64(buffer->size, f); + + nul_run = 0; + for (i = 0; i * buffer->fragment_size < buffer->size; i++) { + if (buffer->fragment[i] == NULL || only_nul(buffer->fragment[i], buffer->fragment_size)) { + nul_run++; + } + else { + if (nul_run > 0) { + write_nuls(nul_run, f); + nul_run = 0; + } + fwrite(MARK_DATA, 4, 1, f); + + fwrite(buffer->fragment[i], 1, buffer->fragment_size, f); + } + } + + if (nul_run > 0) { + write_nuls(nul_run, f); + } + + if (fclose(f) != 0) { + zip_error_set(error, ZIP_ER_WRITE, errno); + return -1; + } + + return 0; +} + + +static zip_int64_t +buffer_write(buffer_t *buffer, const zip_uint8_t *data, zip_uint64_t length, zip_error_t *error) { + zip_uint8_t **fragment; + if (buffer->offset + length > buffer->nfragments * buffer->fragment_size) { + zip_uint64_t needed_fragments = (buffer->offset + length + buffer->fragment_size - 1) / buffer->fragment_size; + zip_uint64_t new_capacity = buffer->nfragments; + zip_uint64_t i; + + if (new_capacity == 0) { + new_capacity = 4; + } + while (new_capacity < needed_fragments) { + new_capacity *= 2; + } + + fragment = realloc(buffer->fragment, new_capacity * sizeof(*fragment)); + + if (fragment == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return -1; + } + + for (i = buffer->nfragments; i < new_capacity; i++) { + fragment[i] = NULL; + } + + buffer->fragment = fragment; + buffer->nfragments = new_capacity; + } + + if (!only_nul(data, length)) { + zip_uint64_t idx, n, fragment_offset; + + idx = buffer->offset / buffer->fragment_size; + fragment_offset = buffer->offset % buffer->fragment_size; + n = 0; + + while (n < length) { + zip_uint64_t left = MY_MIN(length - n, buffer->fragment_size - fragment_offset); + + if (buffer->fragment[idx] == NULL) { + if ((buffer->fragment[idx] = (zip_uint8_t *)malloc(buffer->fragment_size)) == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return -1; + } + memset(buffer->fragment[idx], 0, buffer->fragment_size); + } + memcpy(buffer->fragment[idx] + fragment_offset, data + n, left); + + n += left; + idx++; + fragment_offset = 0; + } + } + + buffer->offset += length; + if (buffer->offset > buffer->size) { + buffer->size = buffer->offset; + } + + return (zip_int64_t)length; +} + + +static zip_uint64_t +get_u64(const zip_uint8_t *b) { + zip_uint64_t i; + + i = (zip_uint64_t)b[0] << 56 | (zip_uint64_t)b[1] << 48 | (zip_uint64_t)b[2] << 40 | (zip_uint64_t)b[3] << 32 | (zip_uint64_t)b[4] << 24 | (zip_uint64_t)b[5] << 16 | (zip_uint64_t)b[6] << 8 | (zip_uint64_t)b[7]; + + return i; +} + + +static int +only_nul(const zip_uint8_t *data, zip_uint64_t length) { + zip_uint64_t i; + + for (i = 0; i < length; i++) { + if (data[i] != '\0') { + return 0; + } + } + + return 1; +} + + +static int +write_nuls(zip_uint64_t n, FILE *f) { + if (fwrite(MARK_NUL, 4, 1, f) != 1) { + return -1; + } + return write_u64(n, f); +} + + +static int +write_u64(zip_uint64_t u64, FILE *f) { + zip_uint8_t b[8]; + + b[0] = (zip_uint8_t)((u64 >> 56) & 0xff); + b[1] = (zip_uint8_t)((u64 >> 48) & 0xff); + b[2] = (zip_uint8_t)((u64 >> 40) & 0xff); + b[3] = (zip_uint8_t)((u64 >> 32) & 0xff); + b[4] = (zip_uint8_t)((u64 >> 24) & 0xff); + b[5] = (zip_uint8_t)((u64 >> 16) & 0xff); + b[6] = (zip_uint8_t)((u64 >> 8) & 0xff); + b[7] = (zip_uint8_t)(u64 & 0xff); + + return fwrite(b, 8, 1, f) == 1 ? 0 : -1; +} + + +static void +hole_free(hole_t *hole) { + if (hole == NULL) { + return; + } + zip_error_fini(&hole->error); + buffer_free(hole->in); + buffer_free(hole->out); + free(hole->fname); + free(hole); +} + + +static hole_t * +hole_new(const char *fname, int flags, zip_error_t *error) { + hole_t *ctx = (hole_t *)malloc(sizeof(*ctx)); + + if (ctx == NULL) { + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + if ((ctx->fname = strdup(fname)) == NULL) { + free(ctx); + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + + if ((ctx->in = buffer_from_file(fname, flags, error)) == NULL) { + free(ctx); + return NULL; + } + + zip_error_init(&ctx->error); + ctx->out = NULL; + + return ctx; +} + + +static zip_int64_t +source_hole_cb(void *ud, void *data, zip_uint64_t length, zip_source_cmd_t command) { + hole_t *ctx = (hole_t *)ud; + + switch (command) { + case ZIP_SOURCE_BEGIN_WRITE: + ctx->out = buffer_new(); + return 0; + + case ZIP_SOURCE_CLOSE: + return 0; + + case ZIP_SOURCE_COMMIT_WRITE: + if (buffer_to_file(ctx->out, ctx->fname, &ctx->error) < 0) { + return -1; + } + buffer_free(ctx->in); + ctx->in = ctx->out; + ctx->out = NULL; + return 0; + + case ZIP_SOURCE_ERROR: + return zip_error_to_data(&ctx->error, data, length); + + case ZIP_SOURCE_FREE: + hole_free(ctx); + return 0; + + case ZIP_SOURCE_OPEN: + ctx->in->offset = 0; + return 0; + + case ZIP_SOURCE_READ: + return buffer_read(ctx->in, data, length, &ctx->error); + + case ZIP_SOURCE_REMOVE: + buffer_free(ctx->in); + ctx->in = buffer_new(); + buffer_free(ctx->out); + ctx->out = NULL; + (void)remove(ctx->fname); + return 0; + + case ZIP_SOURCE_ROLLBACK_WRITE: + buffer_free(ctx->out); + ctx->out = NULL; + return 0; + + case ZIP_SOURCE_SEEK: + return buffer_seek(ctx->in, data, length, &ctx->error); + + case ZIP_SOURCE_SEEK_WRITE: + return buffer_seek(ctx->out, data, length, &ctx->error); + + case ZIP_SOURCE_STAT: { + zip_stat_t *st = ZIP_SOURCE_GET_ARGS(zip_stat_t, data, length, &ctx->error); + + if (st == NULL) { + return -1; + } + + /* TODO: return ENOENT if fname doesn't exist */ + + st->valid |= ZIP_STAT_SIZE; + st->size = ctx->in->size; + return 0; + } + + case ZIP_SOURCE_TELL: + return (zip_int64_t)ctx->in->offset; + + case ZIP_SOURCE_TELL_WRITE: + return (zip_int64_t)ctx->out->offset; + + case ZIP_SOURCE_WRITE: + return buffer_write(ctx->out, data, length, &ctx->error); + + case ZIP_SOURCE_SUPPORTS: + return zip_source_make_command_bitmap(ZIP_SOURCE_BEGIN_WRITE, ZIP_SOURCE_COMMIT_WRITE, ZIP_SOURCE_CLOSE, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_REMOVE, ZIP_SOURCE_ROLLBACK_WRITE, ZIP_SOURCE_SEEK, ZIP_SOURCE_SEEK_WRITE, ZIP_SOURCE_STAT, ZIP_SOURCE_TELL, ZIP_SOURCE_TELL_WRITE, ZIP_SOURCE_WRITE, -1); + + default: + zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } +} diff --git a/core/deps/libzip/regress/stat_index_cp437_guess.test b/core/deps/libzip/regress/stat_index_cp437_guess.test new file mode 100644 index 000000000..4b25e901d --- /dev/null +++ b/core/deps/libzip/regress/stat_index_cp437_guess.test @@ -0,0 +1,150 @@ +# guess CP437 file names and autoconvert them +arguments test-cp437.zip stat 0 stat 1 stat 2 stat 3 stat 4 stat 5 stat 6 stat 7 stat 8 stat 9 stat 10 stat 11 stat 12 stat 13 stat 14 stat 15 +return 0 +file test-cp437.zip test-cp437.zip +stdout +name: '☺☻♥♦♣♠•◘○◙♂♀♪♫☼►' +index: '0' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:50' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '◄↕‼¶§▬↨↑↓→←∟↔▲▼ ' +index: '1' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:54' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '!"#$%&'()*+,-./0' +index: '2' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:58' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '123456789:;<=>?@' +index: '3' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:04' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'ABCDEFGHIJKLMNOP' +index: '4' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:08' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'QRSTUVWXYZ[\]^_`' +index: '5' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:12' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'abcdefghijklmnop' +index: '6' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:18' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'qrstuvwxyz{|}~⌂Ç' +index: '7' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:22' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'üéâäàåçêëèïîìÄÅÉ' +index: '8' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:26' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'æÆôöòûùÿÖÜ¢£¥₧ƒá' +index: '9' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:30' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'íóúñѪº¿⌐¬½¼¡«»░' +index: '10' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:36' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '▒▓│┤╡╢╖╕╣║╗╝╜╛┐└' +index: '11' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:40' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '┴┬├─┼╞╟╚╔╩╦╠═╬╧╨' +index: '12' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:44' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '╤╥╙╘╒╓╫╪┘┌█▄▌▐▀α' +index: '13' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:50' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'ßΓπΣσµτΦΘΩδ∞φε∩≡' +index: '14' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:54' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '±≥≤⌠⌡÷≈°∙·√ⁿ²■  ' +index: '15' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:53:02' +crc: '0' +compression method: '0' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_cp437_raw.test b/core/deps/libzip/regress/stat_index_cp437_raw.test new file mode 100644 index 000000000..6450ad47d --- /dev/null +++ b/core/deps/libzip/regress/stat_index_cp437_raw.test @@ -0,0 +1,150 @@ +# get raw file names them from archive +arguments -x -r test-cp437.zip stat 0 stat 1 stat 2 stat 3 stat 4 stat 5 stat 6 stat 7 stat 8 stat 9 stat 10 stat 11 stat 12 stat 13 stat 14 stat 15 +return 0 +file test-cp437.zip test-cp437.zip +stdout +name: '0102030405060708090a0b0c0d0e0f10' +index: '0' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:50' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '1112131415161718191a1b1c1d1e1f20' +index: '1' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:54' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '2122232425262728292a2b2c2d2e2f30' +index: '2' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:58' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '3132333435363738393a3b3c3d3e3f40' +index: '3' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:04' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '4142434445464748494a4b4c4d4e4f50' +index: '4' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:08' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '5152535455565758595a5b5c5d5e5f60' +index: '5' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:12' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '6162636465666768696a6b6c6d6e6f70' +index: '6' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:18' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '7172737475767778797a7b7c7d7e7f80' +index: '7' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:22' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '8182838485868788898a8b8c8d8e8f90' +index: '8' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:26' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '9192939495969798999a9b9c9d9e9fa0' +index: '9' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:30' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'a1a2a3a4a5a6a7a8a9aaabacadaeafb0' +index: '10' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:36' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'b1b2b3b4b5b6b7b8b9babbbcbdbebfc0' +index: '11' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:40' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'c1c2c3c4c5c6c7c8c9cacbcccdcecfd0' +index: '12' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:44' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'd1d2d3d4d5d6d7d8d9dadbdcdddedfe0' +index: '13' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:50' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'e1e2e3e4e5e6e7e8e9eaebecedeeeff0' +index: '14' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:54' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'f1f2f3f4f5f6f7f8f9fafbfcfdfeffff' +index: '15' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:53:02' +crc: '0' +compression method: '0' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_cp437_strict.test b/core/deps/libzip/regress/stat_index_cp437_strict.test new file mode 100644 index 000000000..51516cab4 --- /dev/null +++ b/core/deps/libzip/regress/stat_index_cp437_strict.test @@ -0,0 +1,150 @@ +# strictly follow ZIP spec and expect CP437 file names, and autoconvert them +arguments -s test-cp437.zip stat 0 stat 1 stat 2 stat 3 stat 4 stat 5 stat 6 stat 7 stat 8 stat 9 stat 10 stat 11 stat 12 stat 13 stat 14 stat 15 +return 0 +file test-cp437.zip test-cp437.zip +stdout +name: '☺☻♥♦♣♠•◘○◙♂♀♪♫☼►' +index: '0' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:50' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '◄↕‼¶§▬↨↑↓→←∟↔▲▼ ' +index: '1' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:54' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '!"#$%&'()*+,-./0' +index: '2' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:51:58' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '123456789:;<=>?@' +index: '3' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:04' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'ABCDEFGHIJKLMNOP' +index: '4' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:08' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'QRSTUVWXYZ[\]^_`' +index: '5' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:12' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'abcdefghijklmnop' +index: '6' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:18' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'qrstuvwxyz{|}~⌂Ç' +index: '7' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:22' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'üéâäàåçêëèïîìÄÅÉ' +index: '8' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:26' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'æÆôöòûùÿÖÜ¢£¥₧ƒá' +index: '9' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:30' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'íóúñѪº¿⌐¬½¼¡«»░' +index: '10' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:36' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '▒▓│┤╡╢╖╕╣║╗╝╜╛┐└' +index: '11' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:40' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '┴┬├─┼╞╟╚╔╩╦╠═╬╧╨' +index: '12' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:44' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '╤╥╙╘╒╓╫╪┘┌█▄▌▐▀α' +index: '13' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:50' +crc: '0' +compression method: '0' +encryption method: '0' + +name: 'ßΓπΣσµτΦΘΩδ∞φε∩≡' +index: '14' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:52:54' +crc: '0' +compression method: '0' +encryption method: '0' + +name: '±≥≤⌠⌡÷≈°∙·√ⁿ²■  ' +index: '15' +size: '0' +compressed size: '0' +mtime: 'Fri Feb 17 2012 20:53:02' +crc: '0' +compression method: '0' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_fileorder.test b/core/deps/libzip/regress/stat_index_fileorder.test new file mode 100644 index 000000000..f50541cc6 --- /dev/null +++ b/core/deps/libzip/regress/stat_index_fileorder.test @@ -0,0 +1,24 @@ +# zip_open: entries ordered by central directory order +arguments fileorder.zzip stat 0 stat 1 +return 0 +file fileorder.zzip fileorder.zip +stdout +name: 'file1' +index: '0' +size: '5' +compressed size: '5' +mtime: 'Fri Apr 27 2012 23:21:42' +crc: '9ee760e5' +compression method: '0' +encryption method: '0' + +name: 'file2' +index: '1' +size: '5' +compressed size: '5' +mtime: 'Fri Apr 27 2012 23:21:44' +crc: '7ee315f' +compression method: '0' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_streamed.test b/core/deps/libzip/regress/stat_index_streamed.test new file mode 100644 index 000000000..1a4b68c1b --- /dev/null +++ b/core/deps/libzip/regress/stat_index_streamed.test @@ -0,0 +1,15 @@ +# stat file in streamed zip file +arguments streamed stat 0 +file streamed streamed.zip +return 0 +stdout +name: '-' +index: '0' +size: '2' +compressed size: '4' +mtime: 'Wed Apr 25 2012 10:20:38' +crc: 'ddeaa107' +compression method: '8' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_streamed_zip64.test b/core/deps/libzip/regress/stat_index_streamed_zip64.test new file mode 100644 index 000000000..79b9c1f0c --- /dev/null +++ b/core/deps/libzip/regress/stat_index_streamed_zip64.test @@ -0,0 +1,15 @@ +# stat file in streamed zip file +arguments streamed stat 0 +file streamed streamed-zip64.zip +return 0 +stdout +name: '-' +index: '0' +size: '2' +compressed size: '4' +mtime: 'Wed Apr 25 2012 10:20:38' +crc: 'ddeaa107' +compression method: '8' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_utf8_guess.test b/core/deps/libzip/regress/stat_index_utf8_guess.test new file mode 100644 index 000000000..1592eabfc --- /dev/null +++ b/core/deps/libzip/regress/stat_index_utf8_guess.test @@ -0,0 +1,15 @@ +# guess UTF-8 file names +arguments test-utf8.zip stat 0 +return 0 +file test-utf8.zip test-utf8.zip +stdout +name: 'ÄÖÜäöüßćçĉéèêëē' +index: '0' +size: '0' +compressed size: '0' +mtime: 'Sat Feb 18 2012 00:15:08' +crc: '0' +compression method: '0' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_utf8_raw.test b/core/deps/libzip/regress/stat_index_utf8_raw.test new file mode 100644 index 000000000..1f6ff3614 --- /dev/null +++ b/core/deps/libzip/regress/stat_index_utf8_raw.test @@ -0,0 +1,15 @@ +# print UTF-8 file names +arguments -r test-utf8.zip stat 0 +return 0 +file test-utf8.zip test-utf8.zip +stdout +name: 'ÄÖÜäöüßćçĉéèêëē' +index: '0' +size: '0' +compressed size: '0' +mtime: 'Sat Feb 18 2012 00:15:08' +crc: '0' +compression method: '0' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_utf8_strict.test b/core/deps/libzip/regress/stat_index_utf8_strict.test new file mode 100644 index 000000000..5f968e6ca --- /dev/null +++ b/core/deps/libzip/regress/stat_index_utf8_strict.test @@ -0,0 +1,16 @@ +# follow strict rules and convert UTF-8 as if it was CP437, but not +# if the files are marked as having UTF-8 names +arguments -s test-utf8.zip stat 0 +return 0 +file test-utf8.zip test-utf8.zip +stdout +name: 'ÄÖÜäöüßćçĉéèêëē' +index: '0' +size: '0' +compressed size: '0' +mtime: 'Sat Feb 18 2012 00:15:08' +crc: '0' +compression method: '0' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_utf8_unmarked_strict.test b/core/deps/libzip/regress/stat_index_utf8_unmarked_strict.test new file mode 100644 index 000000000..640812c00 --- /dev/null +++ b/core/deps/libzip/regress/stat_index_utf8_unmarked_strict.test @@ -0,0 +1,16 @@ +# follow strict rules and convert UTF-8 as if it was CP437, +# if not marked otherwise (in this case: not marked) +arguments -s test-utf8-unmarked.zip stat 0 +return 0 +file test-utf8-unmarked.zip test-utf8-unmarked.zip +stdout +name: '├ä├û├£├ñ├╢├╝├ƒ─ç├º─ë├⌐├¿├¬├½─ô' +index: '0' +size: '0' +compressed size: '0' +mtime: 'Sat Feb 18 2012 00:15:08' +crc: '0' +compression method: '0' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stat_index_zip64.test b/core/deps/libzip/regress/stat_index_zip64.test new file mode 100644 index 000000000..a2a8ddfb2 --- /dev/null +++ b/core/deps/libzip/regress/stat_index_zip64.test @@ -0,0 +1,15 @@ +# stat file in zip64 zip file +arguments bigzero stat 0 +file bigzero bigzero.zip +return 0 +stdout +name: 'bigzero' +index: '0' +size: '4294967296' +compressed size: '4168157' +mtime: 'Thu Mar 15 2012 14:54:06' +crc: 'd202ef8d' +compression method: '8' +encryption method: '0' + +end-of-inline-data diff --git a/core/deps/libzip/regress/stored-no-eos.zip b/core/deps/libzip/regress/stored-no-eos.zip new file mode 100644 index 0000000000000000000000000000000000000000..4a7745f73cda20294873712f5664f92d491b03e4 GIT binary patch literal 348 zcmWIWW@Zs#0Da0vp^AT}!tGmz}_N{9wh%*9TgAsieWw;%dH0CG7CJR*x382FBWFymBh zK53w!WQl7;NpOBzNqJ&XDnogBxn5>oc5!lIL8@MUQTpt6Hc~)E`~f~8u0T39H8mwA zB`qxt$f(Eal|A)72v2n0z9ER(o+c$6H}m?to& z7#T^>bP0l+YC5&B$cWfIAF8Y8e<5z$C5^3GilR11Vqx MLKh&-#0cU605}s>4FCWD literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/streamed-zip64.zip b/core/deps/libzip/regress/streamed-zip64.zip new file mode 100644 index 0000000000000000000000000000000000000000..85886190bef6d6408c2a0f2efbe8418c7f54a61b GIT binary patch literal 148 zcmWIWW@gc4-~htOK{Fi~fZ#tAFfxcR=rS@0fW%OM_Y)?D0B?2<_JyzRvOr~+pmcyY mBa<96!a$fBkUS$)4Tx?K1aWi&yjj^m3`QW-2h#o^4g&yCJ{C>@ literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/streamed.zip b/core/deps/libzip/regress/streamed.zip new file mode 100644 index 0000000000000000000000000000000000000000..737d56f31b3dbf55589c53ce41411c69d2a3cd52 GIT binary patch literal 120 zcmWIWW@gc4-~htOK{Fi~fZ#tAFap`S-cOhq0=(Hd*cZOK%K{W&0^$H~MkYCCgeGJ; dkR}iS(G7wimTrJID;tQx2!#4T+8D%P008cb77YLZ literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test-cp437-comment-utf-8.zip b/core/deps/libzip/regress/test-cp437-comment-utf-8.zip new file mode 100644 index 0000000000000000000000000000000000000000..748a2696743634a8a1f4b5542a1f51d0f01b5942 GIT binary patch literal 2619 zcmaLZX;@5A9LMo9%~V>XBto(iQTAQ3Q)r^HuVvpON)l?bUk@N2FvdPtd-V&?=?|CM|>l1W9=N@kU-m{+Y<-J*tN&00Ju z8!IqL^5aOg>(s4Rzd=K*MvbkTG_|p9#*;Kyfl1O*j?}zG%T}%1v~AbEL&r{??Yh|W zq+G1PBq@L+b?w%@N6%ip`}B2i?AL#QlbR>xV+AHjK^)0>;2;-Qx4}b(4jVpVTjlAvz{DZsVr- z1fEoa6__N2bEL%0TavaWZ`;0O=dRs*_NJurBxS0d6HJmKIMTlT2ht9vA3A*G=&_9B zCr+N?NxE2pNm3L?I-Mz;IeYH>g^QOiU%7hi`VF3Bh!vP5MRTN^w{G9bx_j?__JfBx zk2H^;@FWwgz$7V_BRzeVoA><1%lucb-xR!k_x=MR}{toy|2Tr^LYDV2bs%+j!H3F-2>_ipHCZn>cBUKGn>F|jv zR8B@^vq!2{3WVqgiYgROMr9L7s-9S-gD9#{KN*$HA*p6ym5!sRLJ4J5HjSjJqlFM1 zN>PO>%BXB6NmY$iI+~&ig_Kd*WRhwOR_TC>D%4U&W%Eg@d03@mDymRU8I?^bsoLN_ z8+2Gj6)GyDvRNfnZ>-Xh6;&vzjLIgKR5P(k2Uk>~t}-f{TT(StAw1GN!Kz`sT4Hz`X&M*si- literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test-cp437-fc-utf-8-filename.zip b/core/deps/libzip/regress/test-cp437-fc-utf-8-filename.zip new file mode 100644 index 0000000000000000000000000000000000000000..7aeb8096b35588c0b4f54d83b7cca8033067704b GIT binary patch literal 236 zcmWIWW@h1H0D6LW?UBNGw?ty``ZX&pqY!^y3i0-i20?c h_A)Xtv#_$Ub8rd>3I%wxvVja_1j2G49S`C#006EKLmL19 literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test-cp437-fc.zip b/core/deps/libzip/regress/test-cp437-fc.zip new file mode 100644 index 0000000000000000000000000000000000000000..0c4f04d53139f06a6fc4eb89eecb755ea37aca46 GIT binary patch literal 186 zcmWIWW@h1H0Dbg^Sq?3PX1vdjD z%L`@(29^|{`T%c6COKwY=14H`K+XKy2x6d`$_g(rzFQ0{~}K9<2ZX literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test-cp437.zip b/core/deps/libzip/regress/test-cp437.zip new file mode 100644 index 0000000000000000000000000000000000000000..9e6b91d9a888c4bf7341e7eb67424da945eea8e6 GIT binary patch literal 2582 zcmaLZcT|s29LMqd>-YPW1_}|9O;+|M*%bOkWfQV@L`f)#B$1z}j54wnva_=H3>g`P zkP$LM`QFRvx#xWE^*rbEocqu7I?rG4^XQz^8WTb<-*^`XcKG)&C)Pw|pw?&&jfxnX zn3@$WR@~gp)tIPL6YQPoynRfGN|U7~MB@qX=n5r2fn-slWGTziWvt4UD{o!FreY-~ zrGY{xmHY)#Ap*{XG$w)X8D zn3Mquom2`ENbNgx?9{nS*KXY%oqF`_<*Z{;7ASO5DOe!6^zP&8=H9no{{aIB4IVPo zgGt$-&`G5bfi!IR2+xtDMvoagZv2FalO|7LQVu9|QYlm*P4${K-FwDNAKzJiv;F7H zoyVkHQ0Symm_P~$oFB9xc;TXu#i2{WmWD55QXVLDQYl;@Est2CUm3Y-byRfBnzifJ z$1*7&6gsICA&}xWY}~Xte#_Qv+js2TwL2k^N$Ny~HGk|ii~QYlU#<$U`5CHL#M??3W> z{`#F?P{1S`Q0S1PGO$t03T0IotfT;X#J$q~?bUx&Tq|C61QoXnRAIN2Z^h-}RXwP< zW1tEfrl`1LyqXRwZW^e=VgD2r7m!zVpyD2aDr}*m;u`X5G^n_ZpbERFsJM*0nh7fI zB&fnhDk`oduiC+{18yd$!d@yWE+((~fQtJGs<54kitEX%DWKw(f-3B&qT-VBs+oom k+*MG8O;uD}RbF)m6*m@CVPCa|kPTuEw}+OHSa_F^KYY9@7XSbN literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test-utf8-unmarked.zip b/core/deps/libzip/regress/test-utf8-unmarked.zip new file mode 100644 index 0000000000000000000000000000000000000000..1bcb7035ff928b664d3fe5502a256f45f5f12d75 GIT binary patch literal 210 zcmWIWW@h1H0D&irK@MOBl#pYPVL04!c-rAPhnF1Qc6iU>`A6ChFF(?Gc;(?0hgTh5 zePnWI2qyz`^?f^k5H79YW?*D_!OXzGk^(d#z?+dtjv1H95^#&QG=i9f>|ur2gJB0N P8%Q@J5Q6NC0C5-qK;S&S literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test-utf8.zip b/core/deps/libzip/regress/test-utf8.zip new file mode 100644 index 0000000000000000000000000000000000000000..3045e20b86ae42e8e5529af7803dc62f38278037 GIT binary patch literal 210 zcmWIWW@h1HVBlb2c)}Ruz<>ng7-Sd@w;Y~!c+TM^hqoQxb9nxd_QT7MbRJ%Lc*WsW zhgTn&92&yOz+8Rb&L4zJE4UdLSza(RFtDTmO$qR3WRhdXWwHd^o-K_aCLw!RA@*R{ R!O8~G%?N}bJ0n0G1^`OHJk9_B literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test.zip b/core/deps/libzip/regress/test.zip new file mode 100644 index 0000000000000000000000000000000000000000..e4efd716b64801cd7445a7baaa8d460a661534ca GIT binary patch literal 412 zcmWIWW@h1HU}9ikxLWI`f6R2JH7k$}!YmA;3?-?>C7~gl49t#2jrJfMTEW7Q!XN^c z;0gfi1uA8zo}q07qG5mosy8LGNFS`9Q}l`BEFcEyf5Du>0MUxgT3#frAnT1V><8%w z$pGzVWD;k_<$7MQV;F(tmPQa0;ultkUqUMwfNp?j2T5Xj0HU3NL4e_{qYYF$#5+JE jK;A(%0=q|$T~Ll>1k_t7PGMyOIfEGpPXXyYAPxfnv0Y9$ literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test2.zip b/core/deps/libzip/regress/test2.zip new file mode 100644 index 0000000000000000000000000000000000000000..ed2f60f50e320d2968637f673d0ac50355594b21 GIT binary patch literal 126 zcmWIWW@Zs#0D=6}7FV9S`Wq}jHVE?oaY<@%Nm^!3s$NM&35Xrw&B!Fej9WcW3m9x` X1X0Mk1H4(;Kw^wQXa%J8KpX}DC%P7g literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/test_open_multiple.zip b/core/deps/libzip/regress/test_open_multiple.zip new file mode 100644 index 0000000000000000000000000000000000000000..7dc3091a3f64ab39788b766961ff117bfde2b58a GIT binary patch literal 152 zcmWIWW@h1HW&na0+qQ=UXuV&@0c3+PD-ai#l%}O6CMBn&rln^Dcr!BDGviXM0#ptH z3JpsdK{Qw^7lQ(b&A`YY!O*b#VcPS&8;lLEhGAeD$`9~nWrL_;WC#Z`v_TvO0DTf7 A>i_@% literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testbuffer.zip b/core/deps/libzip/regress/testbuffer.zip new file mode 100644 index 0000000000000000000000000000000000000000..91e7d8a8b84dbd7faaef9afc2a873216a37b6bb4 GIT binary patch literal 180 zcmWIWW@Zs#00FL)7FXuqca+S4Y!K!H;*!+jlH!u0%)E5Hl8Ta$jLc#MAWBpKiR&mN z=A|fPmM9dbrsftal;kU9B$lNrB&DY2DHN9`C#Mz{rYn521qA?I1B(&O)2OA literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testbuffer_reopen.zip b/core/deps/libzip/regress/testbuffer_reopen.zip new file mode 100644 index 0000000000000000000000000000000000000000..a46e72bc7841d0f6763e766558aadc9669f1375c GIT binary patch literal 247 zcmWIWW@Zs#0D)Dpr6Cec{GB{NHVE?paY<@%NpVS0W?s5pNkxf&S!z*vQD#X=Y93br zioUI)(m%L?>R~u9wLC2|Cl#d0Q31$N0P%o&7@6#uaoYwo01UP?f+$qG(e+#$ literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testbzip2.zip b/core/deps/libzip/regress/testbzip2.zip new file mode 100644 index 0000000000000000000000000000000000000000..7c9a9e72b90f5166169f761cf052de78e473b05a GIT binary patch literal 175 zcmWIWW@h1HU|`^32q|&3@o+HRpbzBP05LxhCnhB(>lURJq$ZZ=l~j~CMP*nTI!3Am zM>40JU}0cjcVu7?uwYPNFgR->lvOoVK+W&$eDNYzi+~9r1p(fSOmfV)O$S;427ent W6q;QD-mGjOVMZXd1=2Af4g&znN+PuY literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testchanged.zip b/core/deps/libzip/regress/testchanged.zip new file mode 100644 index 0000000000000000000000000000000000000000..d5169c96edbda1b0e796e0a466feba7a013c1d36 GIT binary patch literal 728 zcmZ|LJxjwt0LJmFO+^D497Nr26Gckj7C{iTxD+JN<^xE2AqUAtlB?Fyv0K5#Nhs(y z@dN1S?&#v+?sG|up%@MId!m8 z2K9<%@GA$=6kyg<2fOy5v5Gf|fvb;Lr`Qo0`8=WcOP^7}ZVoInoCJ~wTrheWyFR}z S!Zj`Pgo|BCIT~`89sd9>TCF1h literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testchangedlocal.zip b/core/deps/libzip/regress/testchangedlocal.zip new file mode 100644 index 0000000000000000000000000000000000000000..1c8b301414483864e522f2d440c53027942d63d8 GIT binary patch literal 714 zcmZ{iF-yZh7>2K=5e;N;5Oup%htirZPJ)U{L=M{g07)+<(B|ZFN*x`$2`)~Bg8mbK zfR65tE)MR#m!xgz#a!Ow2z*c8JcRjH(;@o0e4TXLpEvirIP2JCn?%WwcY^*Vv7UmX zGo4`EBm=Bdkt&|5jEaaxiOdxCA#XWs!DHaPf4n{Jfu%F&9p=1euypEp84pD|Fn~P( zzYcoes|CFrI0a0n4yfWpn!EtKjo-=sYQb*Kn}MZM$BV?RRPmxFhz{gm@ii1|XFS;%9)y{=!{#PZ$uK^NUvM-1D81d0g_CnD*ylh literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testcomment.zip b/core/deps/libzip/regress/testcomment.zip new file mode 100644 index 0000000000000000000000000000000000000000..45dc18d1423248071fd02b8a68e9bed36dd3f623 GIT binary patch literal 703 zcmZ{iKTm@|7>C~q7$spe>UN_#5dU;>GEw8w#*nmp00M`Qw46E6)X}koJZ`34U(Z~~ahB4ClEvdQzoTlpGq&o|iCyeU{Ji+HiPl0`CWu(h;q{O8cY&WT#c z{U`y1SJ=a6c- z1Wzk4Oafe5B{dSBeh7*a2E+@ efee%Mn%5g(v5@+`{<9q0vAZI>aO(+|n0^CjSE923 literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testcomment13.zip b/core/deps/libzip/regress/testcomment13.zip new file mode 100644 index 0000000000000000000000000000000000000000..bd6e1c3277cfc2551406338d2c5f92db0401bb35 GIT binary patch literal 383 zcmWIWW@h1H00FYscEDR|?=$xNd zlA2ditdO6kkd|3gTmmF>QuVk3;N}Gb&3mF1{07}TW3YMifM$U(HuFj{GK*5+<}osf zGvjh0FUa8xj6l4k5yXPJmlfjP&7$w*a5EK1JEEK5~L&d<#ShK)j6ei2w495TFI Ryj(u{dFgsEMrsj|2>{KAS6=`C literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testcommentremoved.zip b/core/deps/libzip/regress/testcommentremoved.zip new file mode 100644 index 0000000000000000000000000000000000000000..b2e4d05dc5bd3d8b4e6d2a4f16215b5c37dc50ff GIT binary patch literal 640 zcmZ{hKTE?v7{=e4M6{4m)Gebr6#sN_RdK0EpuG=JnhOcEIY}ba(XpH0;v^L8H}M1L z=l}lz0zis~IkMA$TvZ&v)Bkapt^3&3gcgQ^zahKxL5y>;m|{ z*Zo*7c%*@kfN|=8Ws>GLPk^`fHQ8M**w(x$Se!autgiDiSrlxnY&!or^zr9J4dm&T zca>4k7!_lpu=6u-JM*lURJq$ZZ=l~j~?`?#>hcxp6g v3x=9WvKBBh1b8zt$uZ;B4>W>-5s3dbf>>y#1$eWvfrJ@>&=yGBfjA5RzZV&5 literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testdeflated2.zip b/core/deps/libzip/regress/testdeflated2.zip new file mode 100644 index 0000000000000000000000000000000000000000..b5ded7d6d3fc7c8a3b59a03ca7b6c3ec5cbfaa1d GIT binary patch literal 270 zcmWIWW@Zs#U|`^2FfX6$=HXzvK@iBZ0b&6lPE1Nn)-6gcNKGs;(krPb@%C|Hi}BQG z&=w3elVmMmWC(z34=HiALDtTXsvU=JMkYCC+|~n)U|O!)Ul?|kV2?$GobO?yU007bbld*r`MfAD_9s_ yFsCpCcr!AIGvhLV7jE8?Mi3L#I97;pp%n~3V_4Zh3K)UV9Z1`QI1GsyiCh3Uv>w<1 literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile-UTF8.zip b/core/deps/libzip/regress/testfile-UTF8.zip new file mode 100644 index 0000000000000000000000000000000000000000..727961545ca745df1f286593bc818d3fb59b996c GIT binary patch literal 126 zcmWIWW@h1HVBlb2Sl;s9hye-k0ojLJ4o^Eg=kWZ)OAc>4yeGh$kx7mjw`!Q?zl|Uk Tx?WZ`kSHS%+5l-C5QhN(9QGVY literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile-cp437.zip b/core/deps/libzip/regress/testfile-cp437.zip new file mode 100644 index 0000000000000000000000000000000000000000..169a90311aa235010b5e29c9479bf3dc81477b76 GIT binary patch literal 130 zcmWIWW@h1H0Dlx4h literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile-ef.zip b/core/deps/libzip/regress/testfile-ef.zip new file mode 100644 index 0000000000000000000000000000000000000000..91551ff60bfcfe7650d0901e42721b03e88d3ec4 GIT binary patch literal 174 zcmWIWW@h1H00Ewf_C8<+l;B~IVJJy0E=kMGN!2TgYC&pZiC#%X38x4vLo5RW z3j>3}o4pGbOJ1Mz>CNQqf6W722He&GZDe3%PykUtfM$7sH!B-Rm=OqVfpjj2!vFwk9WA{8 literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile-torrentzip-modified.zip b/core/deps/libzip/regress/testfile-torrentzip-modified.zip new file mode 100644 index 0000000000000000000000000000000000000000..8be5136b45ad8b7e30ba6ddf1d880bdb0d20d8be GIT binary patch literal 146 zcmWIWW@Zs#U}E54VAwN5kpT#pKm-u*0C7obaYMF}%QfHxyh61QrwHaLN- qH^7^f4J5$`gjPUW2Z+TOLi~e*T>V0#JOct;U3A?{TwGn9jokp3c@?n$ literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile-torrentzip.zip b/core/deps/libzip/regress/testfile-torrentzip.zip new file mode 100644 index 0000000000000000000000000000000000000000..bd59ad3f3362612c6ffa7e93d08d703d031719c1 GIT binary patch literal 146 zcmWIWW@Zs#U}E54VAwN5kpT#pKm-u*0C7obaYMF}%QfHxyh61QrwHaLN- qH^7^f4J5$`gjPUW2Z+TOLi~e*T>V0#JOct;U3A?{TwGn9jU54(bQP=s literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile-xz.zip b/core/deps/libzip/regress/testfile-xz.zip new file mode 100644 index 0000000000000000000000000000000000000000..6be8f9c137882d2dffa709544f84d6b8c0f3f71b GIT binary patch literal 200 zcmWIWW@Zs#U|@)6=%KIhY$$=UxJH&=nA85kH<9b!~g?qe&O rR|VF<5E&KV&B!FefZJNIHULkH literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile-zstd.zip b/core/deps/libzip/regress/testfile-zstd.zip new file mode 100644 index 0000000000000000000000000000000000000000..bf42d3e08bce22cd560e0204ab0235b8865d1bf5 GIT binary patch literal 160 zcmWIWW@fQxU|@)4Sd{rGK;zN91qwi(0}%5Aabi+pvTjjoL26=&UP(oX##a5m3J!Z2 z7%~zQdAX8!xqxIcFBgjd!*Ppah88*j-i%E447iO0n#jP&pa7zP0L}0KZ&o&tFe4D! K0%>0mhXDXy?IKwK literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile.txt b/core/deps/libzip/regress/testfile.txt new file mode 100644 index 000000000..e69de29bb diff --git a/core/deps/libzip/regress/testfile.zip b/core/deps/libzip/regress/testfile.zip new file mode 100644 index 0000000000000000000000000000000000000000..2fa5ba0859f6d7022ecc42226a48f806a2d6e785 GIT binary patch literal 122 zcmWIWW@h1H0DW^-3yA0=yZSHXUXVxj6}Wdn&Z M0-+U<)&g-D033A?-T(jq literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/testfile2014.zip b/core/deps/libzip/regress/testfile2014.zip new file mode 100644 index 0000000000000000000000000000000000000000..1d75d46ecc76e534e984b36e723b64e094390ca3 GIT binary patch literal 122 zcmWIWW@h1H00EKZtgc`Nl;8olE literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/teststdin.zip b/core/deps/libzip/regress/teststdin.zip new file mode 100644 index 0000000000000000000000000000000000000000..e1a40a869fa29ffbe742866eabf0c514ea84545e GIT binary patch literal 200 zcmWIWW@Zs#00Fa<7FXuqca+RPY#`=i5Md}uEiNf8Day=C*DI+gVPp^h%Yapb2p|Z_ z$ShU>qC^FdS{;SNycC7Z5{2T_)ZAi)l6-}X#IjU{q}0?rh2qlW + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "config.h" + +#include +#include + +#ifndef HAVE_GETOPT +#include "getopt.h" +#endif + +#include "zip.h" +#define TRYOPEN_USAGE \ + "usage: %s [-cent] file\n\n" \ + "\t-c\tcheck consistency\n" \ + "\t-e\texclusively open archive\n" \ + "\t-n\tcreate new file\n" \ + "\t-t\ttruncate file to size 0\n" + + +int +main(int argc, char *argv[]) { + const char *fname; + zip_t *z; + int c, flags, ze; + zip_int64_t count; + int error_count; + zip_error_t error; + + flags = 0; + + while ((c = getopt(argc, argv, "cent")) != -1) { + switch (c) { + case 'c': + flags |= ZIP_CHECKCONS; + break; + case 'e': + flags |= ZIP_EXCL; + break; + case 'n': + flags |= ZIP_CREATE; + break; + case 't': + flags |= ZIP_TRUNCATE; + break; + + default: + fprintf(stderr, TRYOPEN_USAGE, argv[0]); + return 1; + } + } + + error_count = 0; + for (; optind < argc; optind++) { + fname = argv[optind]; + errno = 0; + + if ((z = zip_open(fname, flags, &ze)) != NULL) { + count = zip_get_num_entries(z, 0); + printf("opening '%s' succeeded, %" PRIu64 " entries\n", fname, count); + zip_close(z); + continue; + } + + zip_error_init_with_code(&error, ze); + printf("opening '%s' returned error %d", fname, ze); + switch (zip_error_system_type(&error)) { + case ZIP_ET_SYS: + case ZIP_ET_LIBZIP: + printf("/%d", zip_error_code_system(&error)); + break; + + default: + break; + } + printf("\n"); + error_count++; + } + + if (error_count > 0) + fprintf(stderr, "%d errors\n", error_count); + + return error_count ? 1 : 0; +} diff --git a/core/deps/libzip/regress/unchange-delete-namelocate.test b/core/deps/libzip/regress/unchange-delete-namelocate.test new file mode 100644 index 000000000..459d9b5af --- /dev/null +++ b/core/deps/libzip/regress/unchange-delete-namelocate.test @@ -0,0 +1,7 @@ +# namelocate after a file has been deleted and unchanged should succeed. +arguments test2.zip delete 0 unchange 0 name_locate testfile.txt 0 +return 0 +file test2.zip test2.zip +stdout +name 'testfile.txt' using flags '0' found at index 0 +end-of-inline-data diff --git a/core/deps/libzip/regress/utf-8-standardization-input.zip b/core/deps/libzip/regress/utf-8-standardization-input.zip new file mode 100644 index 0000000000000000000000000000000000000000..67e3acf3ce999f23c1694ee994f332976b0df32f GIT binary patch literal 285 zcmWIWW@Zs#U|`^2I8glG<oztux3spkcXW2?l~k0J z7N{^V&b||M87g~t&*7Da7ad-5c){VNhnF8-b$IpR6(Dt=wRH6YHO`&)4b?c|>En0y z#CcCo-5?FmwHgWRF*esdyyx)B!;20tIlSQT(!ijv^`+*F0U)N+O7{JfIXyb^`9{33;tj8uiR%$!uc0B=Sndj?#Fs{oA$ v0R=Eg&?+vdLPiD&hV0Dq?T6Q#XN1uS&VBMAetW^-3yA0=yZS)do@p!i-SW zAi6;i#L*4#W@FQV3d6L4xX4ODe0>lB1f~#*0jQ51#7u$mLFxm%S=nGNgwYHP0A;%+ A`Tzg` literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/zip64.zip b/core/deps/libzip/regress/zip64.zip new file mode 100644 index 0000000000000000000000000000000000000000..c1ba76b7e76183b17d7b058a61ffcea94b3f3e35 GIT binary patch literal 198 zcmWIWW@gc400IA^Xb1L%ukQYb0!9WA23)do@p!i-SW zAi6;i#L*4#W@FQV3d6L4xX4ODe0>lB1f~#*0jQ51#7u$mLFxm%S=m5bMj!;Kho}Vr D!GacW literal 0 HcmV?d00001 diff --git a/core/deps/libzip/regress/zip64_creation.test b/core/deps/libzip/regress/zip64_creation.test new file mode 100644 index 000000000..8b91e9258 --- /dev/null +++ b/core/deps/libzip/regress/zip64_creation.test @@ -0,0 +1,4 @@ +# create big zip64 zip file from scratch +arguments bigzero.zip add_nul bigzero 4294967296 +file bigzero.zip {} bigzero.zip +return 0 diff --git a/core/deps/libzip/regress/zip64_stored_creation.test b/core/deps/libzip/regress/zip64_stored_creation.test new file mode 100644 index 000000000..eccae6c15 --- /dev/null +++ b/core/deps/libzip/regress/zip64_stored_creation.test @@ -0,0 +1,4 @@ +# create big zip64 zip file from scratch +arguments -H bigstored.zh add_nul bigzero 4294967296 set_file_compression 0 0 0 set_file_mtime 0 0 add_nul smallzero 16384 set_file_compression 1 0 0 set_file_mtime 1 0 +file bigstored.zh {} bigstored.zh +return 0 diff --git a/core/deps/libzip/regress/zip_read_fuzzer.cc b/core/deps/libzip/regress/zip_read_fuzzer.cc new file mode 100644 index 000000000..6813807b4 --- /dev/null +++ b/core/deps/libzip/regress/zip_read_fuzzer.cc @@ -0,0 +1,48 @@ +#include + +#ifdef __cplusplus +extern "C" +#endif +int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + zip_source_t *src; + zip_t *za; + zip_error_t error; + char buf[32768]; + zip_int64_t i, n; + zip_file_t *f; + + zip_error_init(&error); + + if ((src = zip_source_buffer_create(data, size, 0, &error)) == NULL) { + zip_error_fini(&error); + return 0; + } + + if ((za = zip_open_from_source(src, 0, &error)) == NULL) { + zip_source_free(src); + zip_error_fini(&error); + return 0; + } + + zip_error_fini(&error); + + n = zip_get_num_entries(za, 0); + + for (i = 0; i < n; i++) { + f = zip_fopen_index(za, i, 0); + if (f == NULL) { + continue; + } + + while (zip_fread(f, buf, sizeof(buf)) > 0) { + ; + } + + zip_fclose(f); + } + + zip_close(za); + + return 0; +} diff --git a/core/deps/libzip/regress/zip_read_fuzzer.dict b/core/deps/libzip/regress/zip_read_fuzzer.dict new file mode 100644 index 000000000..b54ac5235 --- /dev/null +++ b/core/deps/libzip/regress/zip_read_fuzzer.dict @@ -0,0 +1,3 @@ +header_lfh="\x50\x4b\x03\x04" +header_cd="\x50\x4b\x01\x02" +header_eocd="\x50\x4b\x05\x06" diff --git a/core/deps/libzip/regress/zipcmp_zip_dir.test b/core/deps/libzip/regress/zipcmp_zip_dir.test new file mode 100644 index 000000000..8233b80c2 --- /dev/null +++ b/core/deps/libzip/regress/zipcmp_zip_dir.test @@ -0,0 +1,17 @@ +# compare zip with directory +features HAVE_FTS_H +program zipcmp +mkdir a +mkdir a/dir-with-file +mkdir a/empty-dir-in-dir +arguments zipcmp_zip_dir.zip a +file zipcmp_zip_dir.zip zipcmp_zip_dir.zip +return 1 +stdout +--- zipcmp_zip_dir.zip ++++ a +- directory '00-empty-dir/' +- file 'dir-with-file/a', size 1, crc e8b7be43 ++ directory 'empty-dir-in-dir/' +- directory 'empty-dir/' +end-of-inline-data diff --git a/core/deps/libzip/regress/zipcmp_zip_dir.zip b/core/deps/libzip/regress/zipcmp_zip_dir.zip new file mode 100644 index 0000000000000000000000000000000000000000..8f47f21bdf3b8a59450799502bfb13f1e608f62a GIT binary patch literal 483 zcmWIWW@h1H00E~cy&y0HN^mjAFr?-dlvL`bWESa%hHx@4M@umzfN*IAHv=Qf3uXoe zmK31s0Js@JKr@{8ZGXWC6a`^^pcz2jy5*TA8MH5)x5e z!wWRYz(5zxK}XaW5 + +#define ZIP_MIN(a, b) ((a) < (b) ? (a) : (b)) + +#define FOR_REGRESS + +typedef enum { SOURCE_TYPE_NONE, SOURCE_TYPE_IN_MEMORY, SOURCE_TYPE_HOLE } source_type_t; + +source_type_t source_type = SOURCE_TYPE_NONE; +zip_uint64_t fragment_size = 0; +zip_file_t *z_files[16]; +unsigned int z_files_count; +int commands_from_stdin = 0; + +static int add_nul(char *argv[]); +static int cancel(char *argv[]); +static int extract_as(char *argv[]); +static int regress_fopen(char *argv[]); +static int regress_fread(char *argv[]); +static int regress_fseek(char *argv[]); +static int is_seekable(char *argv[]); +static int unchange_one(char *argv[]); +static int unchange_all(char *argv[]); +static int zin_close(char *argv[]); + +#define OPTIONS_REGRESS "F:Himx" + +#define USAGE_REGRESS " [-Himx] [-F fragment-size]" + +#define GETOPT_REGRESS \ + case 'H': \ + source_type = SOURCE_TYPE_HOLE; \ + break; \ + case 'i': \ + commands_from_stdin = 1; \ + break; \ + case 'm': \ + source_type = SOURCE_TYPE_IN_MEMORY; \ + break; \ + case 'F': \ + fragment_size = strtoull(optarg, NULL, 10); \ + break; \ + case 'x': \ + hex_encoded_filenames = 1; \ + break; + +/* clang-format off */ + +#define DISPATCH_REGRESS \ + {"add_nul", 2, "name length", "add NUL bytes", add_nul}, \ + {"cancel", 1, "limit", "cancel writing archive when limit% have been written (calls print_progress)", cancel}, \ + {"extract_as", 2, "index name", "extract file data to given file name", extract_as}, \ + {"fopen", 1, "name", "open archive entry", regress_fopen}, \ + {"fread", 2, "file_index length", "read from fopened file and print", regress_fread}, \ + {"fseek", 3, "file_index offset whence", "seek in fopened file", regress_fseek}, \ + {"is_seekable", 1, "index", "report if entry is seekable", is_seekable}, \ + {"unchange", 1, "index", "revert changes for entry", unchange_one}, \ + {"unchange_all", 0, "", "revert all changes", unchange_all}, \ + {"zin_close", 1, "index", "close input zip_source (for internal tests)", zin_close} + +#define PRECLOSE_REGRESS \ + do { \ + unsigned int file_idx = 0; \ + for (file_idx = 0; file_idx < z_files_count; ++file_idx) { \ + if (zip_fclose (z_files[file_idx]) != 0) { \ + err = 1; \ + } \ + } \ + } \ + while (0) + +/* clang-format on */ + +#define MAX_STDIN_ARGC 128 +#define MAX_STDIN_LENGTH 8192 + +char* stdin_argv[MAX_STDIN_ARGC]; +static char stdin_line[MAX_STDIN_LENGTH]; + +int get_stdin_commands(void); + +#define REGRESS_PREPARE_ARGS \ + if (commands_from_stdin) { \ + argc = get_stdin_commands(); \ + arg = 0; \ + argv = stdin_argv; \ + } + +zip_t *ziptool_open(const char *archive, int flags, zip_error_t *error, zip_uint64_t offset, zip_uint64_t len); + + +#include "ziptool.c" + +int get_stdin_commands(void) { + int argc = 0; + char *p, *word; + fgets(stdin_line, sizeof(stdin_line), stdin); + word = p = stdin_line; + while (1) { + if (*p == ' ' || *p == '\n') { + *p = '\0'; + if (word[0] != '\0') { + stdin_argv[argc] = word; + argc += 1; + if (argc >= MAX_STDIN_ARGC) { + break; + } + } + word = p + 1; + } + else if (*p == '\0') { + if (word[0] != '\0') { + stdin_argv[argc] = word; + argc += 1; + } + break; + } + p += 1; + } + return argc; +} + +zip_source_t *memory_src = NULL; + +static int get_whence(const char *str); +zip_source_t *source_hole_create(const char *, int flags, zip_error_t *); +static zip_t *read_to_memory(const char *archive, int flags, zip_error_t *error, zip_source_t **srcp); +static zip_source_t *source_nul(zip_t *za, zip_uint64_t length); + + +static int +add_nul(char *argv[]) { + zip_source_t *zs; + zip_uint64_t length = strtoull(argv[1], NULL, 10); + + if ((zs = source_nul(za, length)) == NULL) { + fprintf(stderr, "can't create zip_source for length: %s\n", zip_strerror(za)); + return -1; + } + + if (zip_file_add(za, argv[0], zs, 0) == -1) { + zip_source_free(zs); + fprintf(stderr, "can't add file '%s': %s\n", argv[0], zip_strerror(za)); + return -1; + } + return 0; +} + +static int +cancel_callback(zip_t *archive, void *ud) { + if (progress_userdata.percentage >= progress_userdata.limit) { + return -1; + } + return 0; +} + +static int +cancel(char *argv[]) { + zip_int64_t percent; + percent = strtoll(argv[0], NULL, 10); + if (percent > 100 || percent < 0) { + fprintf(stderr, "invalid percentage '%" PRId64 "' for cancel (valid: 0 <= x <= 100)\n", percent); + return -1; + } + progress_userdata.limit = ((double)percent) / 100; + + zip_register_cancel_callback_with_state(za, cancel_callback, NULL, NULL); + + /* needs the percentage updates from print_progress */ + print_progress(argv); + return 0; +} + +static int +extract_as(char *argv[]) { + zip_uint64_t idx; + FILE *fp; + int ret; + + idx = strtoull(argv[0], NULL, 10); + if ((fp=fopen(argv[1], "wb")) == NULL) { + fprintf(stderr, "can't open output file '%s': %s", argv[1], strerror(errno)); + return -1; + } + ret = cat_impl_backend(idx, 0, -1, fp); + if (fclose(fp) != 0) { + fprintf(stderr, "can't close output file '%s': %s", argv[1], strerror(errno)); + ret = -1; + } + return ret; +} + + +static int +is_seekable(char *argv[]) { + zip_uint64_t idx; + zip_file_t *zf; + + idx = strtoull(argv[0], NULL, 10); + if ((zf = zip_fopen_index(za, idx, 0)) == NULL) { + fprintf(stderr, "can't open file at index '%" PRIu64 "': %s\n", idx, zip_strerror(za)); + return -1; + } + switch (zip_file_is_seekable(zf)) { + case -1: + fprintf(stderr, "can't check if file %" PRIu64 " is seekable: %s\n", idx, zip_strerror(za)); + return -1; + case 0: + printf("%" PRIu64 ": NOT seekable\n", idx); + break; + case 1: + printf("%" PRIu64 ": seekable\n", idx); + break; + } + return 0; +} + +static int +regress_fseek(char *argv[]) { + zip_uint64_t file_idx; + zip_file_t *zf; + zip_int64_t offset; + int whence; + + file_idx = strtoull(argv[0], NULL, 10); + offset = strtoll(argv[1], NULL, 10); + whence = get_whence(argv[2]); + if (file_idx >= z_files_count || z_files[file_idx] == NULL) { + fprintf(stderr, "trying to seek in invalid opened file\n"); + return -1; + } + zf = z_files[file_idx]; + + if (zip_fseek(zf, offset, whence) == -1) { + fprintf(stderr, "can't seek in file %" PRIu64 ": %s\n", file_idx, zip_strerror(za)); + return -1; + } + return 0; +} + +static int +unchange_all(char *argv[]) { + if (zip_unchange_all(za) < 0) { + fprintf(stderr, "can't revert changes to archive: %s\n", zip_strerror(za)); + return -1; + } + return 0; +} + + +static int +unchange_one(char *argv[]) { + zip_uint64_t idx; + + idx = strtoull(argv[0], NULL, 10); + + if (zip_unchange(za, idx) < 0) { + fprintf(stderr, "can't revert changes for entry %" PRIu64 ": %s", idx, zip_strerror(za)); + return -1; + } + + return 0; +} + +static int +zin_close(char *argv[]) { + zip_uint64_t idx; + + idx = strtoull(argv[0], NULL, 10); + if (idx >= z_in_count) { + fprintf(stderr, "invalid argument '%" PRIu64 "', only %u zip sources open\n", idx, z_in_count); + return -1; + } + if (zip_close(z_in[idx]) < 0) { + fprintf(stderr, "can't close source archive: %s\n", zip_strerror(z_in[idx])); + return -1; + } + z_in[idx] = z_in[z_in_count]; + z_in_count--; + + return 0; +} + +static int +regress_fopen(char *argv[]) { + if (z_files_count >= (sizeof(z_files) / sizeof(*z_files))) { + fprintf(stderr, "too many open files\n"); + return -1; + } + if ((z_files[z_files_count] = zip_fopen(za, argv[0], 0)) == NULL) { + fprintf(stderr, "can't open entry '%s' from input archive: %s\n", argv[0], zip_strerror(za)); + return -1; + } + printf("opened '%s' as file %u\n", argv[0], z_files_count); + z_files_count += 1; + return 0; +} + + +static int +regress_fread(char *argv[]) { + zip_uint64_t file_idx; + zip_uint64_t length; + char buf[8192]; + zip_int64_t n; + zip_file_t *f; + + file_idx = strtoull(argv[0], NULL, 10); + length = strtoull(argv[1], NULL, 10); + + if (file_idx >= z_files_count || z_files[file_idx] == NULL) { + fprintf(stderr, "trying to read from invalid opened file\n"); + return -1; + } + f = z_files[file_idx]; + while (length > 0) { + zip_uint64_t to_read; + + if (length > sizeof (buf)) { + to_read = sizeof (buf); + } else { + to_read = length; + } + n = zip_fread(f, buf, to_read); + if (n < 0) { + fprintf(stderr, "can't read opened file %" PRIu64 ": %s\n", file_idx, zip_file_strerror(f)); + return -1; + } + if (n == 0) { +#if 0 + fprintf(stderr, "premature end of opened file %" PRIu64 "\n", file_idx); + return -1; +#else + break; +#endif + } + if (fwrite(buf, (size_t)n, 1, stdout) != 1) { + fprintf(stderr, "can't write file contents to stdout: %s\n", strerror(errno)); + return -1; + } + length -= n; + } + return 0; +} + + +static zip_t * +read_hole(const char *archive, int flags, zip_error_t *error) { + zip_source_t *src = NULL; + zip_t *zs = NULL; + + if (strcmp(archive, "/dev/stdin") == 0) { + zip_error_set(error, ZIP_ER_OPNOTSUPP, 0); + return NULL; + } + + if ((src = source_hole_create(archive, flags, error)) == NULL || (zs = zip_open_from_source(src, flags, error)) == NULL) { + zip_source_free(src); + } + + return zs; +} + + +static int get_whence(const char *str) { + if (strcasecmp(str, "set") == 0) { + return SEEK_SET; + } + else if (strcasecmp(str, "cur") == 0) { + return SEEK_CUR; + } + else if (strcasecmp(str, "end") == 0) { + return SEEK_END; + } + else { + return 100; /* invalid */ + } +} + + +static zip_t * +read_to_memory(const char *archive, int flags, zip_error_t *error, zip_source_t **srcp) { + zip_source_t *src; + zip_t *zb; + FILE *fp; + + if (strcmp(archive, "/dev/stdin") == 0) { + zip_error_set(error, ZIP_ER_OPNOTSUPP, 0); + return NULL; + } + + if ((fp = fopen(archive, "rb")) == NULL) { + if (errno == ENOENT) { + src = zip_source_buffer_create(NULL, 0, 0, error); + } + else { + zip_error_set(error, ZIP_ER_OPEN, errno); + return NULL; + } + } + else { + struct stat st; + + if (fstat(fileno(fp), &st) < 0) { + fclose(fp); + zip_error_set(error, ZIP_ER_OPEN, errno); + return NULL; + } + if (fragment_size == 0) { + char *buf; + if ((buf = malloc((size_t)st.st_size)) == NULL) { + fclose(fp); + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + if (fread(buf, (size_t)st.st_size, 1, fp) < 1) { + free(buf); + fclose(fp); + zip_error_set(error, ZIP_ER_READ, errno); + return NULL; + } + src = zip_source_buffer_create(buf, (zip_uint64_t)st.st_size, 1, error); + if (src == NULL) { + free(buf); + } + } + else { + zip_uint64_t nfragments, i, left; + zip_buffer_fragment_t *fragments; + + nfragments = ((size_t)st.st_size + fragment_size - 1) / fragment_size; + if ((fragments = malloc(sizeof(fragments[0]) * nfragments)) == NULL) { + fclose(fp); + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + for (i = 0; i < nfragments; i++) { + left = ZIP_MIN(fragment_size, (size_t)st.st_size - i * fragment_size); + if ((fragments[i].data = malloc(left)) == NULL) { +#ifndef __clang_analyzer__ + /* fragments is initialized up to i - 1*/ + while (--i > 0) { + free(fragments[i].data); + } +#endif + free(fragments); + fclose(fp); + zip_error_set(error, ZIP_ER_MEMORY, 0); + return NULL; + } + fragments[i].length = left; + if (fread(fragments[i].data, left, 1, fp) < 1) { +#ifndef __clang_analyzer__ + /* fragments is initialized up to i - 1*/ + while (--i > 0) { + free(fragments[i].data); + } +#endif + free(fragments); + fclose(fp); + zip_error_set(error, ZIP_ER_READ, errno); + return NULL; + } + } + src = zip_source_buffer_fragment_create(fragments, nfragments, 1, error); + if (src == NULL) { + for (i = 0; i < nfragments; i++) { + free(fragments[i].data); + } + free(fragments); + fclose(fp); + return NULL; + } + free(fragments); + } + fclose(fp); + } + if (src == NULL) { + return NULL; + } + zb = zip_open_from_source(src, flags, error); + if (zb == NULL) { + zip_source_free(src); + return NULL; + } + zip_source_keep(src); + *srcp = src; + return zb; +} + + +typedef struct source_nul { + zip_error_t error; + zip_uint64_t length; + zip_uint64_t offset; +} source_nul_t; + +static zip_int64_t +source_nul_cb(void *ud, void *data, zip_uint64_t length, zip_source_cmd_t command) { + source_nul_t *ctx = (source_nul_t *)ud; + + switch (command) { + case ZIP_SOURCE_CLOSE: + return 0; + + case ZIP_SOURCE_ERROR: + return zip_error_to_data(&ctx->error, data, length); + + case ZIP_SOURCE_FREE: + free(ctx); + return 0; + + case ZIP_SOURCE_OPEN: + ctx->offset = 0; + return 0; + + case ZIP_SOURCE_READ: + if (length > ZIP_INT64_MAX) { + zip_error_set(&ctx->error, ZIP_ER_INVAL, 0); + return -1; + } + + if (length > ctx->length - ctx->offset) { + length = ctx->length - ctx->offset; + } + + memset(data, 0, length); + ctx->offset += length; + return (zip_int64_t)length; + + case ZIP_SOURCE_STAT: { + zip_stat_t *st = ZIP_SOURCE_GET_ARGS(zip_stat_t, data, length, &ctx->error); + + if (st == NULL) { + return -1; + } + + st->valid |= ZIP_STAT_SIZE; + st->size = ctx->length; + + return 0; + } + + case ZIP_SOURCE_SUPPORTS: + return zip_source_make_command_bitmap(ZIP_SOURCE_CLOSE, ZIP_SOURCE_ERROR, ZIP_SOURCE_FREE, ZIP_SOURCE_OPEN, ZIP_SOURCE_READ, ZIP_SOURCE_STAT, -1); + + default: + zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0); + return -1; + } +} + +static zip_source_t * +source_nul(zip_t *zs, zip_uint64_t length) { + source_nul_t *ctx; + zip_source_t *src; + + if ((ctx = (source_nul_t *)malloc(sizeof(*ctx))) == NULL) { + zip_error_set(zip_get_error(zs), ZIP_ER_MEMORY, 0); + return NULL; + } + + zip_error_init(&ctx->error); + ctx->length = length; + ctx->offset = 0; + + if ((src = zip_source_function(zs, source_nul_cb, ctx)) == NULL) { + free(ctx); + return NULL; + } + + return src; +} + + +static int +write_memory_src_to_file(const char *archive, zip_source_t *src) { + zip_stat_t zst; + char *buf; + FILE *fp; + + if (zip_source_stat(src, &zst) < 0) { + fprintf(stderr, "zip_source_stat on buffer failed: %s\n", zip_error_strerror(zip_source_error(src))); + return -1; + } + if (zip_source_open(src) < 0) { + if (zip_error_code_zip(zip_source_error(src)) == ZIP_ER_DELETED) { + if (unlink(archive) < 0 && errno != ENOENT) { + fprintf(stderr, "unlink failed: %s\n", strerror(errno)); + return -1; + } + return 0; + } + fprintf(stderr, "zip_source_open on buffer failed: %s\n", zip_error_strerror(zip_source_error(src))); + return -1; + } + if ((buf = malloc(zst.size)) == NULL) { + fprintf(stderr, "malloc failed: %s\n", strerror(errno)); + zip_source_close(src); + return -1; + } + if (zip_source_read(src, buf, zst.size) < (zip_int64_t)zst.size) { + fprintf(stderr, "zip_source_read on buffer failed: %s\n", zip_error_strerror(zip_source_error(src))); + zip_source_close(src); + free(buf); + return -1; + } + zip_source_close(src); + if ((fp = fopen(archive, "wb")) == NULL) { + fprintf(stderr, "fopen failed: %s\n", strerror(errno)); + free(buf); + return -1; + } + if (fwrite(buf, zst.size, 1, fp) < 1) { + fprintf(stderr, "fwrite failed: %s\n", strerror(errno)); + free(buf); + fclose(fp); + return -1; + } + free(buf); + if (fclose(fp) != 0) { + fprintf(stderr, "fclose failed: %s\n", strerror(errno)); + return -1; + } + return 0; +} + + +zip_t * +ziptool_open(const char *archive, int flags, zip_error_t *error, zip_uint64_t offset, zip_uint64_t len) { + switch (source_type) { + case SOURCE_TYPE_NONE: + za = read_from_file(archive, flags, error, offset, len); + break; + + case SOURCE_TYPE_IN_MEMORY: + za = read_to_memory(archive, flags, error, &memory_src); + break; + + case SOURCE_TYPE_HOLE: + za = read_hole(archive, flags, error); + break; + } + + return za; +} + + +int +ziptool_post_close(const char *archive) { + if (source_type == SOURCE_TYPE_IN_MEMORY) { + if (write_memory_src_to_file(archive, memory_src) < 0) { + return -1; + } + zip_source_free(memory_src); + } + + return 0; +} diff --git a/core/deps/libzip/src/CMakeLists.txt b/core/deps/libzip/src/CMakeLists.txt new file mode 100644 index 000000000..a0f53252c --- /dev/null +++ b/core/deps/libzip/src/CMakeLists.txt @@ -0,0 +1,14 @@ +check_function_exists(getopt HAVE_GETOPT) +foreach(PROGRAM zipcmp zipmerge ziptool) + add_executable(${PROGRAM} ${PROGRAM}.c) + target_link_libraries(${PROGRAM} zip) + target_include_directories(${PROGRAM} PRIVATE BEFORE ${PROJECT_SOURCE_DIR}/lib ${PROJECT_BINARY_DIR}) + if(LIBZIP_DO_INSTALL) + install(TARGETS ${PROGRAM} EXPORT ${PROJECT_NAME}-targets DESTINATION bin) + endif() + if(NOT HAVE_GETOPT) + target_sources(${PROGRAM} PRIVATE getopt.c) + endif(NOT HAVE_GETOPT) +endforeach() +target_sources(zipcmp PRIVATE diff_output.c) +target_link_libraries(zipcmp ${FTS_LIB} ZLIB::ZLIB) diff --git a/core/deps/libzip/src/diff_output.c b/core/deps/libzip/src/diff_output.c new file mode 100644 index 000000000..96b6313b1 --- /dev/null +++ b/core/deps/libzip/src/diff_output.c @@ -0,0 +1,106 @@ +#include "diff_output.h" + +#include +#include +#include +#include + +#include "compat.h" + +static void ensure_header(diff_output_t *output) { + if (output->archive_names[0] != NULL) { + printf("--- %s\n", output->archive_names[0]); + printf("+++ %s\n", output->archive_names[1]); + output->archive_names[0] = NULL; + output->archive_names[1] = NULL; + } +} + +void diff_output_init(diff_output_t *output, int verbose, char *const archive_names[]) { + output->archive_names[0] = archive_names[0]; + output->archive_names[1] = archive_names[1]; + output->verbose = verbose; + output->file_name = NULL; + output->file_size = 0; + output->file_crc = 0; +} + +void diff_output_start_file(diff_output_t *output, const char *name, zip_uint64_t size, zip_uint32_t crc) { + output->file_name = name; + output->file_size = size; + output->file_crc = crc; +} + +void diff_output_end_file(diff_output_t *output) { + output->file_name = NULL; +} + +void diff_output(diff_output_t *output, int side, const char *fmt, ...) { + va_list ap; + + if (!output->verbose) { + return; + } + + ensure_header(output); + + if (output->file_name != NULL) { + diff_output_file(output, ' ', output->file_name, output->file_size, output->file_crc); + output->file_name = NULL; + } + + printf("%c ", side); + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + printf("\n"); +} + +void diff_output_file(diff_output_t *output, char side, const char *name, zip_uint64_t size, zip_uint32_t crc) { + if (!output->verbose) { + return; + } + + ensure_header(output); + + if (size == 0 && crc == 0 && name[0] != '\0' && name[strlen(name) - 1] == '/') { + printf("%c directory '%s'\n", side, name); + } + else { + printf("%c file '%s', size %" PRIu64 ", crc %08x\n", side, name, size, crc); + } +} + +#define MAX_BYTES 64 +void diff_output_data(diff_output_t *output, int side, const zip_uint8_t *data, zip_uint64_t data_length, const char *fmt, ...) { + char prefix[1024]; + char hexdata[MAX_BYTES * 3 + 6]; + size_t i, offset; + va_list ap; + + if (!output->verbose) { + return; + } + + offset = 0; + for (i = 0; i < data_length; i++) { + hexdata[offset++] = (i == 0 ? '<' : ' '); + + if (i >= MAX_BYTES) { + snprintf(hexdata + offset, sizeof(hexdata) - offset, "..."); + break; + } + snprintf(hexdata + offset, sizeof(hexdata) - offset, "%02x", data[i]); + offset += 2; + } + + hexdata[offset++] = '>'; + hexdata[offset] = '\0'; + + va_start(ap, fmt); + vsnprintf(prefix, sizeof(prefix), fmt, ap); + va_end(ap); + prefix[sizeof(prefix) - 1] = '\0'; + + diff_output(output, side, "%s, length %" PRIu64 ", data %s", prefix, data_length, hexdata); +} diff --git a/core/deps/libzip/src/diff_output.h b/core/deps/libzip/src/diff_output.h new file mode 100644 index 000000000..8ac12b8bb --- /dev/null +++ b/core/deps/libzip/src/diff_output.h @@ -0,0 +1,28 @@ +#ifndef HAD_DIFF_OUTPUT_H +#define HAD_DIFF_OUTPUT_H + +#include + +typedef struct { + const char *archive_names[2]; + const char *file_name; + zip_uint64_t file_size; + zip_uint32_t file_crc; + int verbose; +} diff_output_t; + +#if defined(__GNUC__) && __GNUC__ >= 4 +#define PRINTF_LIKE(n, m) __attribute__((__format__(__printf__, n, m))) +#else +#define PRINTF_LIKE(n, m) +#endif + +void diff_output_init(diff_output_t *output, int verbose, char *const archive_names[]); +void diff_output_start_file(diff_output_t *output, const char *name, zip_uint64_t size, zip_uint32_t crc); +void diff_output_end_file(diff_output_t *output); + +void diff_output(diff_output_t *output, int side, const char *fmt, ...) PRINTF_LIKE(3, 4); +void diff_output_data(diff_output_t *output, int side, const zip_uint8_t *data, zip_uint64_t data_length, const char *fmt, ...) PRINTF_LIKE(5, 6); +void diff_output_file(diff_output_t *output, char side, const char *name, zip_uint64_t size, zip_uint32_t crc); + +#endif /* HAD_DIFF_OUTPUT_H */ diff --git a/core/deps/libzip/src/getopt.c b/core/deps/libzip/src/getopt.c new file mode 100644 index 000000000..a9879c4bc --- /dev/null +++ b/core/deps/libzip/src/getopt.c @@ -0,0 +1,110 @@ +/* + * getopt.c -- + * + * Standard UNIX getopt function. Code is from BSD. + * + * Copyright (c) 1987-2002 The Regents of the University of California. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * A. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * B. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * C. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS + * IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* #if !defined(lint) + * static char sccsid[] = "@(#)getopt.c 8.2 (Berkeley) 4/2/94"; + * #endif + */ +#include +#include +#include + +#include "getopt.h" + +int opterr = 1, /* if error message should be printed */ + optind = 1, /* index into parent argv vector */ + optopt, /* character checked for validity */ + optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +#define BADCH (int)'?' +#define BADARG (int)':' +#define EMSG "" + +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +getopt(int nargc, char *const *nargv, const char *ostr) { + static char *place = EMSG; /* option letter processing */ + char *oli; /* option letter list index */ + + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc || *(place = nargv[optind]) != '-') { + place = EMSG; + return (EOF); + } + if (place[1] && *++place == '-') { /* found "--" */ + ++optind; + place = EMSG; + return (EOF); + } + } /* option letter okay? */ + if ((optopt = (int)*place++) == (int)':' || !(oli = (char *)strchr(ostr, optopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means EOF. + */ + if (optopt == (int)'-') + return (EOF); + if (!*place) + ++optind; + if (opterr && *ostr != ':') + (void)fprintf(stderr, "illegal option -- %c\n", optopt); + return (BADCH); + } + if (*++oli != ':') { /* don't need argument */ + optarg = NULL; + if (!*place) + ++optind; + } + else { /* need an argument */ + if (*place) /* no white space */ + optarg = place; + else if (nargc <= ++optind) { /* no arg */ + place = EMSG; + if (*ostr == ':') + return (BADARG); + if (opterr) + (void)fprintf(stderr, "option requires an argument -- %c\n", optopt); + return (BADCH); + } + else /* white space */ + optarg = nargv[optind]; + place = EMSG; + ++optind; + } + return (optopt); /* dump back option letter */ +} diff --git a/core/deps/libzip/src/getopt.h b/core/deps/libzip/src/getopt.h new file mode 100644 index 000000000..cc2ef3661 --- /dev/null +++ b/core/deps/libzip/src/getopt.h @@ -0,0 +1,51 @@ +#ifndef _HAD_GETOPT_H +#define _HAD_GETOPT_H + +/* + getopt.h -- header for getopt() replacement function + Copyright (C) 1999-2021 Dieter Baron and Thomas Klausner + + This file is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifdef __cplusplus +extern "C" { +#endif + +extern char *optarg; +extern int optind; +extern int opterr; + +extern int getopt(int, char *const *, const char *); + +#ifdef __cplusplus +} +#endif + +#endif /* _HAD_GETOPT_H */ diff --git a/core/deps/libzip/src/zipcmp.c b/core/deps/libzip/src/zipcmp.c new file mode 100644 index 000000000..695c77e42 --- /dev/null +++ b/core/deps/libzip/src/zipcmp.c @@ -0,0 +1,880 @@ +/* + zipcmp.c -- compare zip files + Copyright (C) 2003-2022 Dieter Baron and Thomas Klausner + + This file is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "config.h" + +#include +#include +#include +#include +#include +#ifdef HAVE_STRINGS_H +#include +#endif +#ifdef HAVE_FTS_H +#include +#endif +#include + +#ifndef HAVE_GETOPT +#include "getopt.h" +#endif + +#include "zip.h" + +#include "compat.h" + +#include "diff_output.h" + +struct archive { + const char *name; + zip_t *za; + zip_uint64_t nentry; + struct entry *entry; + const char *comment; + size_t comment_length; +}; + +struct ef { + const char *name; + zip_uint16_t flags; + zip_uint16_t id; + zip_uint16_t size; + const zip_uint8_t *data; +}; + +struct entry { + char *name; + zip_uint64_t size; + zip_uint32_t crc; + zip_uint32_t comp_method; + struct ef *extra_fields; + zip_uint16_t n_extra_fields; + const char *comment; + zip_uint32_t comment_length; +}; + + +typedef struct { + uint32_t value; + const char * const name; +} enum_map_t; + +const enum_map_t comp_methods[] = { + { 0, "Stored (no compression)" }, + { 1, "Shrunk" }, + { 2, "Reduced with compression factor 1" }, + { 3, "Reduced with compression factor 2" }, + { 4, "Reduced with compression factor 3" }, + { 5, "Reduced with compression factor 4" }, + { 6, "Imploded" }, + { 7, "Reserved for Tokenizing compression algorithm" }, + { 8, "Deflated" }, + { 9, "Enhanced Deflating using Deflate64(tm)" }, + { 10, "PKWARE Data Compression Library Imploding (old IBM TERSE)" }, + { 11, "11 (Reserved by PKWARE)" }, + { 12, "BZIP2" }, + { 13, "13 (Reserved by PKWARE)" }, + { 14, "LZMA (EFS)" }, + { 15, "15 (Reserved by PKWARE)" }, + { 16, "16 (Reserved by PKWARE)" }, + { 17, "17 (Reserved by PKWARE)" }, + { 18, "IBM TERSE (new)" }, + { 19, "IBM LZ77 z Architecture (PFS)" }, + { 20, "Zstandard compressed data (obsolete)" }, + { 93, "Zstandard compressed data" }, + { 95, "XZ compressed data" }, + { 97, "WavPack compressed data" }, + { 98, "PPMd version I, Rev 1" }, + { 99, "WinZIP AES Encryption" }, + { UINT32_MAX, NULL } +}; + +const enum_map_t extra_fields[] = { + /* PKWARE defined */ + { 0x0001, "Zip64 extended information" }, + { 0x0007, "AV Info" }, + { 0x0008, "Reserved for extended language encoding data (PFS)" }, + { 0x0009, "OS/2" }, + { 0x000a, "NTFS" }, + { 0x000c, "OpenVMS" }, + { 0x000d, "UNIX" }, + { 0x000e, "Reserved for file stream and fork descriptors" }, + { 0x000f, "Patch Descriptor" }, + { 0x0014, "PKCS#7 Store for X.509 Certificates" }, + { 0x0015, "X.509 Certificate ID and Signature for individual file" }, + { 0x0016, "X.509 Certificate ID for Central Directory" }, + { 0x0017, "Strong Encryption Header" }, + { 0x0018, "Record Management Controls" }, + { 0x0019, "PKCS#7 Encryption Recipient Certificate List" }, + { 0x0065, "IBM S/390 (Z390), AS/400 (I400) attributes - uncompressed" }, + { 0x0066, "Reserved for IBM S/390 (Z390), AS/400 (I400) attributes - compressed" }, + { 0x4690, "POSZIP 4690 (reserved)" }, + + /* Third-Party defined; see InfoZIP unzip sources proginfo/extrafld.txt */ + { 0x07c8, "Info-ZIP Macintosh (old)" }, + { 0x2605, "ZipIt Macintosh (first version)" }, + { 0x2705, "ZipIt Macintosh 1.3.5+ (w/o full filename)" }, + { 0x2805, "ZipIt Macintosh 1.3.5+" }, + { 0x334d, "Info-ZIP Macintosh (new)" }, + { 0x4154, "Tandem NSK" }, + { 0x4341, "Acorn/SparkFS" }, + { 0x4453, "Windows NT security descriptor" }, + { 0x4704, "VM/CMS" }, + { 0x470f, "MVS" }, + { 0x4854, "Theos, old unofficial port" }, + { 0x4b46, "FWKCS MD5" }, + { 0x4c41, "OS/2 access control list (text ACL)" }, + { 0x4d49, "Info-ZIP OpenVMS (obsolete)" }, + { 0x4d63, "Macintosh SmartZIP" }, + { 0x4f4c, "Xceed original location extra field" }, + { 0x5356, "AOS/VS (ACL)" }, + { 0x5455, "extended timestamp" }, + { 0x554e, "Xceed unicode extra field" }, + { 0x5855, "Info-ZIP UNIX (original)" }, + { 0x6375, "Info-ZIP UTF-8 comment field" }, + { 0x6542, "BeOS (BeBox, PowerMac, etc.)" }, + { 0x6854, "Theos" }, + { 0x7075, "Info-ZIP UTF-8 name field" }, + { 0x7441, "AtheOS (AtheOS/Syllable attributes)" }, + { 0x756e, "ASi UNIX" }, + { 0x7855, "Info-ZIP UNIX" }, + { 0x7875, "Info-ZIP UNIX 3rd generation" }, + { 0x9901, "WinZIP AES encryption" }, + { 0xa220, "Microsoft Open Packaging Growth Hint" }, + { 0xcafe, "executable Java JAR file" }, + { 0xfb4a, "SMS/QDOS" }, /* per InfoZIP extrafld.txt */ + { 0xfd4a, "SMS/QDOS" }, /* per appnote.txt */ + { UINT32_MAX, NULL } +}; + + +const char *progname; + +#define PROGRAM "zipcmp" + +#define USAGE "usage: %s [-hipqtVv] archive1 archive2\n" + +char help_head[] = PROGRAM " (" PACKAGE ") by Dieter Baron and Thomas Klausner\n\n"; + +char help[] = "\n\ + -h display this help message\n\ + -C check archive consistencies\n\ + -i compare names ignoring case distinctions\n\ + -p compare as many details as possible\n\ + -q be quiet\n\ + -s print a summary\n\ + -t test zip files (compare file contents to checksum)\n\ + -V display version number\n\ + -v be verbose (print differences, default)\n\ +\n\ +Report bugs to .\n"; + +char version_string[] = PROGRAM " (" PACKAGE " " VERSION ")\n\ +Copyright (C) 2003-2022 Dieter Baron and Thomas Klausner\n\ +" PACKAGE " comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n"; + +#define OPTIONS "hVCipqstv" + + +#define BOTH_ARE_ZIPS(a) (a[0].za && a[1].za) + +static int comment_compare(const char *c1, size_t l1, const char *c2, size_t l2); +static int compare_list(char *const name[2], const void *list[2], const zip_uint64_t list_length[2], int element_size, int (*cmp)(const void *a, const void *b), int (*ignore)(const void *list, int last, const void *other), int (*check)(char *const name[2], const void *a, const void *b), void (*print)(char side, const void *element), void (*start_file)(const void *element)); +static int compare_zip(char *const zn[]); +static int ef_compare(char *const name[2], const struct entry *e1, const struct entry *e2); +static int ef_order(const void *a, const void *b); +static void ef_print(char side, const void *p); +static int ef_read(zip_t *za, zip_uint64_t idx, struct entry *e); +static int entry_cmp(const void *p1, const void *p2); +static int entry_ignore(const void *p1, int last, const void *o); +static int entry_paranoia_checks(char *const name[2], const void *p1, const void *p2); +static void entry_print(char side, const void *p); +static void entry_start_file(const void *p); +static const char *map_enum(const enum_map_t *map, uint32_t value); + +static int is_directory(const char *name); +#ifdef HAVE_FTS_H +static int list_directory(const char *name, struct archive *a); +#endif +static int list_zip(const char *name, struct archive *a); +static int test_file(zip_t *za, zip_uint64_t idx, const char *zipname, const char *filename, zip_uint64_t size, zip_uint32_t crc); + +int ignore_case, test_files, paranoid, verbose, have_directory, check_consistency, summary; +int plus_count = 0, minus_count = 0; + +diff_output_t output; + + +int +main(int argc, char *const argv[]) { + int c; + + progname = argv[0]; + + ignore_case = 0; + test_files = 0; + check_consistency = 0; + paranoid = 0; + have_directory = 0; + verbose = 1; + summary = 0; + + while ((c = getopt(argc, argv, OPTIONS)) != -1) { + switch (c) { + case 'C': + check_consistency = 1; + break; + case 'i': + ignore_case = 1; + break; + case 'p': + paranoid = 1; + break; + case 'q': + verbose = 0; + break; + case 's': + summary = 1; + break; + case 't': + test_files = 1; + break; + case 'v': + verbose = 1; + break; + + case 'h': + fputs(help_head, stdout); + printf(USAGE, progname); + fputs(help, stdout); + exit(0); + case 'V': + fputs(version_string, stdout); + exit(0); + + default: + fprintf(stderr, USAGE, progname); + exit(2); + } + } + + if (argc != optind + 2) { + fprintf(stderr, USAGE, progname); + exit(2); + } + + exit((compare_zip(argv + optind) == 0) ? 0 : 1); +} + + +static int +compare_zip(char *const zn[]) { + struct archive a[2]; + struct entry *e[2]; + zip_uint64_t n[2]; + int i; + int res; + + for (i = 0; i < 2; i++) { + a[i].name = zn[i]; + a[i].entry = NULL; + a[i].nentry = 0; + a[i].za = NULL; + a[i].comment = NULL; + a[i].comment_length = 0; + + if (is_directory(zn[i])) { +#ifndef HAVE_FTS_H + fprintf(stderr, "%s: reading directories not supported\n", progname); + exit(2); +#else + if (list_directory(zn[i], a + i) < 0) + exit(2); + have_directory = 1; + paranoid = 0; /* paranoid checks make no sense for directories, since they compare zip metadata */ +#endif + } + else { + if (list_zip(zn[i], a + i) < 0) + exit(2); + } + if (a[i].nentry > 0) + qsort(a[i].entry, a[i].nentry, sizeof(a[i].entry[0]), entry_cmp); + } + + diff_output_init(&output, verbose, zn); + + e[0] = a[0].entry; + e[1] = a[1].entry; + n[0] = a[0].nentry; + n[1] = a[1].nentry; + res = compare_list(zn, (const void **)e, n, sizeof(e[i][0]), entry_cmp, have_directory ? entry_ignore : NULL, paranoid ? entry_paranoia_checks : NULL, entry_print, entry_start_file); + + if (paranoid) { + if (comment_compare(a[0].comment, a[0].comment_length, a[1].comment, a[1].comment_length) != 0) { + if (a[0].comment_length > 0) { + diff_output_data(&output, '-', (const zip_uint8_t *)a[0].comment, a[0].comment_length, "archive comment"); + minus_count++; + } + if (a[1].comment_length > 0) { + diff_output_data(&output, '+', (const zip_uint8_t *)a[1].comment, a[1].comment_length, "archive comment"); + plus_count++; + } + res = 1; + } + } + + for (i = 0; i < 2; i++) { + zip_uint64_t j; + + if (a[i].za) { + zip_close(a[i].za); + } + for (j = 0; j < a[i].nentry; j++) { + free(a[i].entry[j].name); + } + free(a[i].entry); + } + + if (summary) { + printf("%d files removed, %d files added\n", minus_count, plus_count); + } + + switch (res) { + case 0: + exit(0); + + case 1: + exit(1); + + default: + exit(2); + } +} + +#ifdef HAVE_FTS_H +static zip_int64_t +compute_crc(const char *fname) { + FILE *f; + uLong crc = crc32(0L, Z_NULL, 0); + size_t n; + Bytef buffer[8192]; + + + if ((f = fopen(fname, "rb")) == NULL) { + fprintf(stderr, "%s: can't open %s: %s\n", progname, fname, strerror(errno)); + return -1; + } + + while ((n = fread(buffer, 1, sizeof(buffer), f)) > 0) { + crc = crc32(crc, buffer, (unsigned int)n); + } + + if (ferror(f)) { + fprintf(stderr, "%s: read error on %s: %s\n", progname, fname, strerror(errno)); + fclose(f); + return -1; + } + + fclose(f); + + return (zip_int64_t)crc; +} +#endif + + +static int +is_directory(const char *name) { + struct stat st; + + if (stat(name, &st) < 0) + return 0; + + return S_ISDIR(st.st_mode); +} + + +#ifdef HAVE_FTS_H +static int +list_directory(const char *name, struct archive *a) { + FTS *fts; + FTSENT *ent; + zip_uint64_t nalloc; + size_t prefix_length; + + char *const names[2] = {(char *)name, NULL}; + + + if ((fts = fts_open(names, FTS_NOCHDIR | FTS_LOGICAL, NULL)) == NULL) { + fprintf(stderr, "%s: can't open directory '%s': %s\n", progname, name, strerror(errno)); + return -1; + } + prefix_length = strlen(name) + 1; + + nalloc = 0; + + while ((ent = fts_read(fts))) { + zip_int64_t crc; + + switch (ent->fts_info) { + case FTS_DOT: + case FTS_DP: + case FTS_DEFAULT: + case FTS_SL: + case FTS_NSOK: + break; + + case FTS_DC: + case FTS_DNR: + case FTS_ERR: + case FTS_NS: + case FTS_SLNONE: + /* TODO: error */ + fts_close(fts); + return -1; + + case FTS_D: + case FTS_F: + if (a->nentry >= nalloc) { + nalloc += 16; + if (nalloc > SIZE_MAX / sizeof(a->entry[0])) { + fprintf(stderr, "%s: malloc failure\n", progname); + exit(1); + } + a->entry = realloc(a->entry, sizeof(a->entry[0]) * nalloc); + if (a->entry == NULL) { + fprintf(stderr, "%s: malloc failure\n", progname); + exit(1); + } + } + + if (ent->fts_info == FTS_D) { + char *dir_name; + + if (ent->fts_path[prefix_length - 1] == '\0') { + break; + } + + size_t dir_name_size = strlen(ent->fts_path + prefix_length) + 2; + dir_name = malloc(dir_name_size); + if (dir_name == NULL) { + fprintf(stderr, "%s: malloc failure\n", progname); + exit(1); + } + snprintf(dir_name, dir_name_size, "%s/", ent->fts_path + prefix_length); + a->entry[a->nentry].name = dir_name; + a->entry[a->nentry].size = 0; + a->entry[a->nentry].crc = 0; + } + else { + a->entry[a->nentry].name = strdup(ent->fts_path + prefix_length); + a->entry[a->nentry].size = (zip_uint64_t)ent->fts_statp->st_size; + if ((crc = compute_crc(ent->fts_accpath)) < 0) { + fts_close(fts); + return -1; + } + + a->entry[a->nentry].crc = (zip_uint32_t)crc; + } + a->nentry++; + break; + } + } + + if (fts_close(fts)) { + fprintf(stderr, "%s: error closing directory '%s': %s\n", progname, a->name, strerror(errno)); + return -1; + } + + return 0; +} +#endif + + +static int +list_zip(const char *name, struct archive *a) { + zip_t *za; + int err; + struct zip_stat st; + unsigned int i; + + if ((za = zip_open(name, check_consistency ? ZIP_CHECKCONS : 0, &err)) == NULL) { + zip_error_t error; + zip_error_init_with_code(&error, err); + fprintf(stderr, "%s: cannot open zip archive '%s': %s\n", progname, name, zip_error_strerror(&error)); + zip_error_fini(&error); + return -1; + } + + a->za = za; + a->nentry = (zip_uint64_t)zip_get_num_entries(za, 0); + + if (a->nentry == 0) + a->entry = NULL; + else { + if ((a->nentry > SIZE_MAX / sizeof(a->entry[0])) || (a->entry = (struct entry *)malloc(sizeof(a->entry[0]) * a->nentry)) == NULL) { + fprintf(stderr, "%s: malloc failure\n", progname); + exit(1); + } + + for (i = 0; i < a->nentry; i++) { + zip_stat_index(za, i, 0, &st); + a->entry[i].name = strdup(st.name); + a->entry[i].size = st.size; + a->entry[i].crc = st.crc; + if (test_files) + test_file(za, i, name, st.name, st.size, st.crc); + if (paranoid) { + a->entry[i].comp_method = st.comp_method; + ef_read(za, i, a->entry + i); + a->entry[i].comment = zip_file_get_comment(za, i, &a->entry[i].comment_length, 0); + } + else { + a->entry[i].comp_method = 0; + a->entry[i].n_extra_fields = 0; + } + } + + if (paranoid) { + int length; + a->comment = zip_get_archive_comment(za, &length, 0); + a->comment_length = (size_t)length; + } + else { + a->comment = NULL; + a->comment_length = 0; + } + } + + return 0; +} + + +static int +comment_compare(const char *c1, size_t l1, const char *c2, size_t l2) { + if (l1 != l2) + return 1; + + if (l1 == 0) + return 0; + + if (c1 == NULL || c2 == NULL) + return c1 == c2; + + return memcmp(c1, c2, (size_t)l2); +} + + +static int compare_list(char *const name[2], const void *list[2], const zip_uint64_t list_length[2], int element_size, int (*cmp)(const void *a, const void *b), int (*ignore)(const void *list, int last, const void *other), int (*check)(char *const name[2], const void *a, const void *b), void (*print)(char side, const void *element), void (*start_file)(const void *element)) { + unsigned int i[2]; + int j; + int diff; + +#define INC(k) (i[k]++, list[k] = ((const char *)list[k]) + element_size) +#define PRINT(k) \ + do { \ + if (ignore && ignore(list[k], i[k] >= list_length[k] - 1, i[1-k] < list_length[1-k] ? list[1-k] : NULL)) { \ + break; \ + } \ + print((k) ? '+' : '-', list[k]); \ + (k) ? plus_count++ : minus_count++; \ + diff = 1; \ + } while (0) + + i[0] = i[1] = 0; + diff = 0; + while (i[0] < list_length[0] && i[1] < list_length[1]) { + int c = cmp(list[0], list[1]); + + if (c == 0) { + if (check) { + if (start_file) { + start_file(list[0]); + } + diff |= check(name, list[0], list[1]); + if (start_file) { + diff_output_end_file(&output); + } + } + INC(0); + INC(1); + } + else if (c < 0) { + PRINT(0); + INC(0); + } + else { + PRINT(1); + INC(1); + } + } + + for (j = 0; j < 2; j++) { + while (i[j] < list_length[j]) { + PRINT(j); + INC(j); + } + } + + return diff; +} + + +static int +ef_read(zip_t *za, zip_uint64_t idx, struct entry *e) { + zip_int16_t n_local, n_central; + zip_uint16_t i; + + if ((n_local = zip_file_extra_fields_count(za, idx, ZIP_FL_LOCAL)) < 0 || (n_central = zip_file_extra_fields_count(za, idx, ZIP_FL_CENTRAL)) < 0) { + return -1; + } + + e->n_extra_fields = (zip_uint16_t)(n_local + n_central); + + if ((e->extra_fields = (struct ef *)malloc(sizeof(e->extra_fields[0]) * e->n_extra_fields)) == NULL) + return -1; + + for (i = 0; i < n_local; i++) { + e->extra_fields[i].name = e->name; + e->extra_fields[i].data = zip_file_extra_field_get(za, idx, i, &e->extra_fields[i].id, &e->extra_fields[i].size, ZIP_FL_LOCAL); + if (e->extra_fields[i].data == NULL) + return -1; + e->extra_fields[i].flags = ZIP_FL_LOCAL; + } + for (; i < e->n_extra_fields; i++) { + e->extra_fields[i].name = e->name; + e->extra_fields[i].data = zip_file_extra_field_get(za, idx, (zip_uint16_t)(i - n_local), &e->extra_fields[i].id, &e->extra_fields[i].size, ZIP_FL_CENTRAL); + if (e->extra_fields[i].data == NULL) + return -1; + e->extra_fields[i].flags = ZIP_FL_CENTRAL; + } + + qsort(e->extra_fields, e->n_extra_fields, sizeof(e->extra_fields[0]), ef_order); + + return 0; +} + + +static int +ef_compare(char *const name[2], const struct entry *e1, const struct entry *e2) { + struct ef *ef[2]; + zip_uint64_t n[2]; + + ef[0] = e1->extra_fields; + ef[1] = e2->extra_fields; + n[0] = e1->n_extra_fields; + n[1] = e2->n_extra_fields; + + return compare_list(name, (const void **)ef, n, sizeof(struct ef), ef_order, NULL, NULL, ef_print, NULL); +} + + +static int +ef_order(const void *ap, const void *bp) { + const struct ef *a, *b; + + a = (struct ef *)ap; + b = (struct ef *)bp; + + if (a->flags != b->flags) + return a->flags - b->flags; + if (a->id != b->id) + return a->id - b->id; + if (a->size != b->size) + return a->size - b->size; + return memcmp(a->data, b->data, a->size); +} + + +static void +ef_print(char side, const void *p) { + const struct ef *ef = (struct ef *)p; + + diff_output_data(&output, side, ef->data, ef->size, " %s extra field %s", ef->flags == ZIP_FL_LOCAL ? "local" : "central", map_enum(extra_fields, ef->id)); +} + + +static int +entry_cmp(const void *p1, const void *p2) { + const struct entry *e1, *e2; + int c; + + e1 = (struct entry *)p1; + e2 = (struct entry *)p2; + + if ((c = (ignore_case ? strcasecmp : strcmp)(e1->name, e2->name)) != 0) + return c; + if (e1->size != e2->size) { + if (e1->size > e2->size) + return 1; + else + return -1; + } + if (e1->crc != e2->crc) + return (int)e1->crc - (int)e2->crc; + + return 0; +} + + +static int +entry_ignore(const void *p, int last, const void *o) { + const struct entry *e = (const struct entry *)p; + const struct entry *other = (const struct entry *)o; + + size_t length = strlen(e[0].name); + + if (length == 0 || e[0].name[length - 1] != '/') { + /* not a directory */ + return 0; + } + + if (other != NULL && strlen(other->name) > length && strncmp(other->name, e[0].name, length) == 0) { + /* not empty in other archive */ + return 1; + } + + if (last || (strlen(e[1].name) < length || strncmp(e[0].name, e[1].name, length) != 0)) { + /* empty in this archive */ + return 0; + } + + /* not empty in this archive */ + return 1; +} + + +static int +entry_paranoia_checks(char *const name[2], const void *p1, const void *p2) { + const struct entry *e1, *e2; + int ret; + + e1 = (struct entry *)p1; + e2 = (struct entry *)p2; + + ret = 0; + + if (e1->comp_method != e2->comp_method) { + diff_output(&output, '-', " compression method %s", map_enum(comp_methods, e1->comp_method)); + diff_output(&output, '+', " compression method %s", map_enum(comp_methods, e2->comp_method)); + ret = 1; + } + + if (ef_compare(name, e1, e2) != 0) { + ret = 1; + } + + if (comment_compare(e1->comment, e1->comment_length, e2->comment, e2->comment_length) != 0) { + diff_output_data(&output, '-', (const zip_uint8_t *)e1->comment, e1->comment_length, " comment"); + diff_output_data(&output, '+', (const zip_uint8_t *)e2->comment, e2->comment_length, " comment"); + ret = 1; + } + + return ret; +} + + +static void entry_print(char side, const void *p) { + const struct entry *e = (struct entry *)p; + + diff_output_file(&output, side, e->name, e->size, e->crc); +} + + +static void entry_start_file(const void *p) { + const struct entry *e = (struct entry *)p; + + diff_output_start_file(&output, e->name, e->size, e->crc); +} + + +static int +test_file(zip_t *za, zip_uint64_t idx, const char *zipname, const char *filename, zip_uint64_t size, zip_uint32_t crc) { + zip_file_t *zf; + char buf[8192]; + zip_uint64_t nsize; + zip_int64_t n; + zip_uint32_t ncrc; + + if ((zf = zip_fopen_index(za, idx, 0)) == NULL) { + fprintf(stderr, "%s: %s: cannot open file %s (index %" PRIu64 "): %s\n", progname, zipname, filename, idx, zip_strerror(za)); + return -1; + } + + ncrc = (zip_uint32_t)crc32(0, NULL, 0); + nsize = 0; + + while ((n = zip_fread(zf, buf, sizeof(buf))) > 0) { + nsize += (zip_uint64_t)n; + ncrc = (zip_uint32_t)crc32(ncrc, (const Bytef *)buf, (unsigned int)n); + } + + if (n < 0) { + fprintf(stderr, "%s: %s: error reading file %s (index %" PRIu64 "): %s\n", progname, zipname, filename, idx, zip_file_strerror(zf)); + zip_fclose(zf); + return -1; + } + + zip_fclose(zf); + + if (nsize != size) { + fprintf(stderr, "%s: %s: file %s (index %" PRIu64 "): unexpected length %" PRId64 " (should be %" PRId64 ")\n", progname, zipname, filename, idx, nsize, size); + return -2; + } + if (ncrc != crc) { + fprintf(stderr, "%s: %s: file %s (index %" PRIu64 "): unexpected length %x (should be %x)\n", progname, zipname, filename, idx, ncrc, crc); + return -2; + } + + return 0; +} + + +static const char *map_enum(const enum_map_t *map, uint32_t value) { + static char unknown[16]; + size_t i = 0; + + while (map[i].value < UINT32_MAX) { + if (map[i].value == value) { + return map[i].name; + } + i++; + } + + snprintf(unknown, sizeof(unknown), "unknown (%u)", value); + unknown[sizeof(unknown) - 1] = '\0'; + + return unknown; +} diff --git a/core/deps/libzip/src/zipmerge.c b/core/deps/libzip/src/zipmerge.c new file mode 100644 index 000000000..fca5b01f9 --- /dev/null +++ b/core/deps/libzip/src/zipmerge.c @@ -0,0 +1,336 @@ +/* + zipmerge.c -- merge zip archives + Copyright (C) 2004-2021 Dieter Baron and Thomas Klausner + + This file is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include +#include +#include +#include + +#include "config.h" + +#ifndef HAVE_GETOPT +#include "getopt.h" +#endif + +#include "zip.h" + +char *progname; + +#define PROGRAM "zipmerge" + +#define USAGE "usage: %s [-DhIikSsV] target-zip zip...\n" + +char help_head[] = PROGRAM " (" PACKAGE ") by Dieter Baron and Thomas Klausner\n\n"; + +char help[] = "\n\ + -h display this help message\n\ + -V display version number\n\ + -D ignore directory component in file names\n\ + -I ignore case in file names\n\ + -i ask before overwriting files\n\ + -k don't compress when adding uncompressed files\n\ + -S don't overwrite identical files\n\ + -s overwrite identical files without asking\n\ +\n\ +Report bugs to .\n"; + +char version_string[] = PROGRAM " (" PACKAGE " " VERSION ")\n\ +Copyright (C) 2004-2022 Dieter Baron and Thomas Klausner\n\ +" PACKAGE " comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n"; + +#define OPTIONS "hVDiIksS" + +#define CONFIRM_ALL_YES 0x001 +#define CONFIRM_ALL_NO 0x002 +#define CONFIRM_SAME_YES 0x010 +#define CONFIRM_SAME_NO 0x020 + +int confirm; +zip_flags_t name_flags; +int keep_stored; + +static int confirm_replace(zip_t *, const char *, zip_uint64_t, zip_t *, const char *, zip_uint64_t); +static void copy_extra_fields(zip_t *destination_archive, zip_uint64_t destination_index, zip_t *source_archive, zip_uint64_t source_index, zip_flags_t flags); +static int copy_file(zip_t *destination_archive, zip_int64_t destination_index, zip_t *source_archive, zip_uint64_t source_index, const char* name); +static zip_t *merge_zip(zip_t *, const char *, const char *); + + +int +main(int argc, char *argv[]) { + zip_t *za; + zip_t **zs; + int c, err; + unsigned int i, n; + char *tname; + + progname = argv[0]; + + confirm = CONFIRM_ALL_YES; + name_flags = 0; + keep_stored = 0; + + while ((c = getopt(argc, argv, OPTIONS)) != -1) { + switch (c) { + case 'D': + name_flags |= ZIP_FL_NODIR; + break; + case 'I': + name_flags |= ZIP_FL_NOCASE; + break; + case 'i': + confirm &= ~CONFIRM_ALL_YES; + break; + case 'k': + keep_stored = 1; + break; + case 'S': + confirm &= ~CONFIRM_SAME_YES; + confirm |= CONFIRM_SAME_NO; + break; + case 's': + confirm &= ~CONFIRM_SAME_NO; + confirm |= CONFIRM_SAME_YES; + break; + + case 'h': + fputs(help_head, stdout); + printf(USAGE, progname); + fputs(help, stdout); + exit(0); + case 'V': + fputs(version_string, stdout); + exit(0); + + default: + fprintf(stderr, USAGE, progname); + exit(2); + } + } + + if (argc < optind + 2) { + fprintf(stderr, USAGE, progname); + exit(2); + } + + tname = argv[optind++]; + argv += optind; + + n = (unsigned int)(argc - optind); + if ((zs = (zip_t **)malloc(sizeof(zs[0]) * n)) == NULL) { + fprintf(stderr, "%s: out of memory\n", progname); + exit(1); + } + + if ((za = zip_open(tname, ZIP_CREATE, &err)) == NULL) { + zip_error_t error; + zip_error_init_with_code(&error, err); + fprintf(stderr, "%s: can't open zip archive '%s': %s\n", progname, tname, zip_error_strerror(&error)); + zip_error_fini(&error); + exit(1); + } + + for (i = 0; i < n; i++) { + if ((zs[i] = merge_zip(za, tname, argv[i])) == NULL) + exit(1); + } + + if (zip_close(za) < 0) { + fprintf(stderr, "%s: cannot write zip archive '%s': %s\n", progname, tname, zip_strerror(za)); + exit(1); + } + + for (i = 0; i < n; i++) + zip_close(zs[i]); + + exit(0); +} + + +static int +confirm_replace(zip_t *za, const char *tname, zip_uint64_t it, zip_t *zs, const char *sname, zip_uint64_t is) { + char line[1024]; + struct zip_stat st, ss; + + if (confirm & CONFIRM_ALL_YES) + return 1; + else if (confirm & CONFIRM_ALL_NO) + return 0; + + if (zip_stat_index(za, it, ZIP_FL_UNCHANGED, &st) < 0) { + fprintf(stderr, "%s: cannot stat file %" PRIu64 " in '%s': %s\n", progname, it, tname, zip_strerror(za)); + return -1; + } + if (zip_stat_index(zs, is, 0, &ss) < 0) { + fprintf(stderr, "%s: cannot stat file %" PRIu64 " in '%s': %s\n", progname, is, sname, zip_strerror(zs)); + return -1; + } + + if (st.size == ss.size && st.crc == ss.crc) { + if (confirm & CONFIRM_SAME_YES) + return 1; + else if (confirm & CONFIRM_SAME_NO) + return 0; + } + + printf("replace '%s' (%" PRIu64 " / %08x) in `%s'\n" + " with '%s' (%" PRIu64 " / %08x) from `%s'? ", + st.name, st.size, st.crc, tname, ss.name, ss.size, ss.crc, sname); + fflush(stdout); + + if (fgets(line, sizeof(line), stdin) == NULL) { + fprintf(stderr, "%s: read error from stdin: %s\n", progname, strerror(errno)); + return -1; + } + + if (tolower((unsigned char)line[0]) == 'y') + return 1; + + return 0; +} + + +static zip_t * +merge_zip(zip_t *za, const char *tname, const char *sname) { + zip_t *zs; + zip_int64_t ret, idx; + zip_uint64_t i; + int err; + const char *fname; + + if ((zs = zip_open(sname, 0, &err)) == NULL) { + zip_error_t error; + zip_error_init_with_code(&error, err); + fprintf(stderr, "%s: can't open zip archive '%s': %s\n", progname, sname, zip_error_strerror(&error)); + zip_error_fini(&error); + return NULL; + } + + ret = zip_get_num_entries(zs, 0); + if (ret < 0) { + fprintf(stderr, "%s: cannot get number of entries for '%s': %s\n", progname, sname, zip_strerror(za)); + return NULL; + } + for (i = 0; i < (zip_uint64_t)ret; i++) { + fname = zip_get_name(zs, i, 0); + + if ((idx = zip_name_locate(za, fname, name_flags)) >= 0) { + switch (confirm_replace(za, tname, (zip_uint64_t)idx, zs, sname, i)) { + case 0: + break; + + case 1: + if (copy_file(za, idx, zs, i, NULL) < 0) { + fprintf(stderr, "%s: cannot replace '%s' in `%s': %s\n", progname, fname, tname, zip_strerror(za)); + zip_close(zs); + return NULL; + } + break; + + case -1: + zip_close(zs); + return NULL; + + default: + fprintf(stderr, + "%s: internal error: " + "unexpected return code from confirm (%d)\n", + progname, err); + zip_close(zs); + return NULL; + } + } + else { + if (copy_file(za, -1, zs, i, fname) < 0) { + fprintf(stderr, "%s: cannot add '%s' to `%s': %s\n", progname, fname, tname, zip_strerror(za)); + zip_close(zs); + return NULL; + } + } + } + + return zs; +} + + +static int copy_file(zip_t *destination_archive, zip_int64_t destination_index, zip_t *source_archive, zip_uint64_t source_index, const char* name) { + zip_source_t *source = zip_source_zip_file(destination_archive, source_archive, source_index, ZIP_FL_COMPRESSED, 0, -1, NULL); + + if (source == NULL) { + return -1; + } + + if (destination_index >= 0) { + if (zip_file_replace(destination_archive, (zip_uint64_t)destination_index, source, 0) < 0) { + zip_source_free(source); + return -1; + } + } + else { + destination_index = zip_file_add(destination_archive, name, source, 0); + if (destination_index < 0) { + zip_source_free(source); + return -1; + } + } + + copy_extra_fields(destination_archive, (zip_uint64_t)destination_index, source_archive, source_index, ZIP_FL_CENTRAL); + copy_extra_fields(destination_archive, (zip_uint64_t)destination_index, source_archive, source_index, ZIP_FL_LOCAL); + if (keep_stored) { + zip_stat_t st; + if (zip_stat_index(source_archive, source_index, 0, &st) == 0 && (st.valid & ZIP_STAT_COMP_METHOD) && st.comp_method == ZIP_CM_STORE) { + zip_set_file_compression(destination_archive, destination_index, ZIP_CM_STORE, 0); + } + } + + return 0; +} + + +static void copy_extra_fields(zip_t *destination_archive, zip_uint64_t destination_index, zip_t *source_archive, zip_uint64_t source_index, zip_flags_t flags) { + zip_int16_t n; + zip_uint16_t i, id, length; + const zip_uint8_t *data; + + if ((n = zip_file_extra_fields_count(source_archive, source_index, flags)) < 0) { + return; + } + + for (i = 0; i < n; i++) { + if ((data = zip_file_extra_field_get(source_archive, source_index, i, &id, &length, flags)) == NULL) { + continue; + } + zip_file_extra_field_set(destination_archive, destination_index, id, ZIP_EXTRA_FIELD_NEW, data, length, flags); + } +} diff --git a/core/deps/libzip/src/ziptool.c b/core/deps/libzip/src/ziptool.c new file mode 100644 index 000000000..281bfbbb7 --- /dev/null +++ b/core/deps/libzip/src/ziptool.c @@ -0,0 +1,1161 @@ +/* + ziptool.c -- tool for modifying zip archive in multiple ways + Copyright (C) 2012-2022 Dieter Baron and Thomas Klausner + + This file is part of libzip, a library to manipulate ZIP archives. + The authors can be contacted at + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + 3. The names of the authors may not be used to endorse or promote + products derived from this software without specific prior + written permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN + IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include "config.h" + +#include "compat.h" + +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef _WIN32 +/* WIN32 needs for _O_BINARY */ +#include +#ifndef STDIN_FILENO +#define STDIN_FILENO _fileno(stdin) +#endif +#endif + +#ifndef HAVE_GETOPT +#include "getopt.h" +#endif +extern int optopt; + +#include "zip.h" + +typedef struct dispatch_table_s { + const char *cmdline_name; + int argument_count; + const char *arg_names; + const char *description; + int (*function)(char *argv[]); +} dispatch_table_t; + +static zip_flags_t get_flags(const char *arg); +static zip_int32_t get_compression_method(const char *arg); +static zip_uint16_t get_encryption_method(const char *arg); +static void hexdump(const zip_uint8_t *data, zip_uint16_t len); +static int parse_archive_flag(const char* arg); +int ziptool_post_close(const char *archive); +static const char* decode_filename(const char* name); +static const char* encode_filename(const char* name); + +#ifndef FOR_REGRESS +#define OPTIONS_REGRESS "" +#define USAGE_REGRESS "" +#endif + +zip_t *za, *z_in[16]; +unsigned int z_in_count; +zip_flags_t stat_flags; +int hex_encoded_filenames = 0; // Can only be set in ziptool_regress. + +static int +cat_impl_backend(zip_uint64_t idx, zip_uint64_t start, zip_uint64_t len, FILE *out) { + zip_error_t error; + zip_source_t *src; + zip_int64_t n; + char buf[8192]; + + zip_error_init(&error); + /* we can't pass 0 as a len to zip_source_zip_create because it + will try to give us compressed data */ + if (len == 0) { + struct zip_stat sb; + + if (zip_stat_index(za, idx, stat_flags, &sb) < 0) { + fprintf(stderr, "zip_stat_index failed on '%" PRIu64 "' failed: %s\n", idx, zip_strerror(za)); + return -1; + } + + if (!(sb.valid & ZIP_STAT_SIZE)) { + fprintf(stderr, "can't cat file at index '%" PRIu64 "' with unknown size\n", idx); + return -1; + } + len = sb.size; + } + if ((src = zip_source_zip_file_create(za, idx, 0, start, len, NULL, &error)) == NULL) { + fprintf(stderr, "can't open file at index '%" PRIu64 "': %s\n", idx, zip_error_strerror(&error)); + zip_error_fini(&error); + return -1; + } + zip_error_fini(&error); + + if (zip_source_open(src) < 0) { + fprintf(stderr, "can't open file at index '%" PRIu64 "': %s\n", idx, zip_error_strerror(zip_source_error(src))); + zip_source_free(src); + return -1; + } + while ((n = zip_source_read(src, buf, sizeof(buf))) > 0) { + if (fwrite(buf, (size_t)n, 1, out) != 1) { + fprintf(stderr, "can't write file contents: %s\n", strerror(errno)); + zip_source_free(src); + return -1; + } + } + if (n == -1) { + fprintf(stderr, "can't read file at index '%" PRIu64 "': %s\n", idx, zip_error_strerror(zip_source_error(src))); + zip_source_free(src); + return -1; + } + if (zip_source_close(src) < 0) { + fprintf(stderr, "can't close file at index '%" PRIu64 "': %s\n", idx, zip_error_strerror(zip_source_error(src))); + zip_source_free(src); + return -1; + } + zip_source_free(src); + + return 0; +} + +static int +cat_impl(zip_uint64_t idx, zip_uint64_t start, zip_uint64_t len) { +#ifdef _WIN32 + /* Need to set stdout to binary mode for Windows */ + setmode(fileno(stdout), _O_BINARY); +#endif + return cat_impl_backend(idx, start, len, stdout); +} + +static int +add(char *argv[]) { + zip_source_t *zs; + + if ((zs = zip_source_buffer(za, argv[1], strlen(argv[1]), 0)) == NULL) { + fprintf(stderr, "can't create zip_source from buffer: %s\n", zip_strerror(za)); + return -1; + } + + if (zip_file_add(za, decode_filename(argv[0]), zs, 0) == -1) { + zip_source_free(zs); + fprintf(stderr, "can't add file '%s': %s\n", argv[0], zip_strerror(za)); + return -1; + } + return 0; +} + +static int +add_dir(char *argv[]) { + /* add directory */ + if (zip_dir_add(za, decode_filename(argv[0]), 0) < 0) { + fprintf(stderr, "can't add directory '%s': %s\n", argv[0], zip_strerror(za)); + return -1; + } + return 0; +} + +static int +add_file(char *argv[]) { + zip_source_t *zs; + zip_uint64_t start = strtoull(argv[2], NULL, 10); + zip_int64_t len = strtoll(argv[3], NULL, 10); + + if (strcmp(argv[1], "/dev/stdin") == 0) { + if ((zs = zip_source_filep(za, stdin, start, len)) == NULL) { + fprintf(stderr, "can't create zip_source from stdin: %s\n", zip_strerror(za)); + return -1; + } + } + else { + if ((zs = zip_source_file(za, argv[1], start, len)) == NULL) { + fprintf(stderr, "can't create zip_source from file: %s\n", zip_strerror(za)); + return -1; + } + } + + if (zip_file_add(za, decode_filename(argv[0]), zs, 0) == -1) { + zip_source_free(zs); + fprintf(stderr, "can't add file '%s': %s\n", argv[0], zip_strerror(za)); + return -1; + } + return 0; +} + +static int +add_from_zip(char *argv[]) { + zip_uint64_t idx, start; + zip_int64_t len; + int err; + zip_source_t *zs; + zip_flags_t flags = 0; + /* add from another zip file */ + idx = strtoull(argv[2], NULL, 10); + start = strtoull(argv[3], NULL, 10); + len = strtoll(argv[4], NULL, 10); + if ((z_in[z_in_count] = zip_open(argv[1], ZIP_CHECKCONS, &err)) == NULL) { + zip_error_t error; + zip_error_init_with_code(&error, err); + fprintf(stderr, "can't open zip archive '%s': %s\n", argv[1], zip_error_strerror(&error)); + zip_error_fini(&error); + return -1; + } + if (start == 0 && len == -1) { + flags = ZIP_FL_COMPRESSED; + } + if ((zs = zip_source_zip_file(za, z_in[z_in_count], idx, flags, start, len, NULL)) == NULL) { + fprintf(stderr, "error creating file source from '%s' index '%" PRIu64 "': %s\n", argv[1], idx, zip_strerror(za)); + zip_close(z_in[z_in_count]); + return -1; + } + if (zip_file_add(za, decode_filename(argv[0]), zs, 0) == -1) { + fprintf(stderr, "can't add file '%s': %s\n", argv[0], zip_strerror(za)); + zip_source_free(zs); + zip_close(z_in[z_in_count]); + return -1; + } + z_in_count++; + return 0; +} + +static int +cat(char *argv[]) { + /* output file contents to stdout */ + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + + return cat_impl(idx, 0, 0); +} + +static int +cat_partial(char *argv[]) { + /* output partial file contents to stdout */ + zip_uint64_t idx; + zip_uint64_t start; + zip_uint64_t len; + + idx = strtoull(argv[0], NULL, 10); + start = strtoull(argv[1], NULL, 10); + len = strtoull(argv[2], NULL, 10); + + return cat_impl(idx, start, len); +} + +static int +count_extra(char *argv[]) { + zip_int16_t count; + zip_uint64_t idx; + zip_flags_t ceflags = 0; + idx = strtoull(argv[0], NULL, 10); + ceflags = get_flags(argv[1]); + if ((count = zip_file_extra_fields_count(za, idx, ceflags)) < 0) { + fprintf(stderr, "can't get extra field count for file at index '%" PRIu64 "': %s\n", idx, zip_strerror(za)); + return -1; + } + else { + printf("Extra field count: %d\n", count); + } + return 0; +} + +static int +count_extra_by_id(char *argv[]) { + zip_int16_t count; + zip_uint16_t eid; + zip_flags_t ceflags = 0; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + eid = (zip_uint16_t)strtoull(argv[1], NULL, 10); + ceflags = get_flags(argv[2]); + if ((count = zip_file_extra_fields_count_by_id(za, idx, eid, ceflags)) < 0) { + fprintf(stderr, "can't get extra field count for file at index '%" PRIu64 "' and for id '%d': %s\n", idx, eid, zip_strerror(za)); + return -1; + } + else { + printf("Extra field count: %d\n", count); + } + return 0; +} + +static int delete (char *argv[]) { + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + if (zip_delete(za, idx) < 0) { + fprintf(stderr, "can't delete file at index '%" PRIu64 "': %s\n", idx, zip_strerror(za)); + return -1; + } + return 0; +} + +static int +delete_extra(char *argv[]) { + zip_flags_t geflags; + zip_uint16_t eid; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + eid = (zip_uint16_t)strtoull(argv[1], NULL, 10); + geflags = get_flags(argv[2]); + if ((zip_file_extra_field_delete(za, idx, eid, geflags)) < 0) { + fprintf(stderr, "can't delete extra field data for file at index '%" PRIu64 "', extra field id '%d': %s\n", idx, eid, zip_strerror(za)); + return -1; + } + return 0; +} + +static int +delete_extra_by_id(char *argv[]) { + zip_flags_t geflags; + zip_uint16_t eid, eidx; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + eid = (zip_uint16_t)strtoull(argv[1], NULL, 10); + eidx = (zip_uint16_t)strtoull(argv[2], NULL, 10); + geflags = get_flags(argv[3]); + if ((zip_file_extra_field_delete_by_id(za, idx, eid, eidx, geflags)) < 0) { + fprintf(stderr, "can't delete extra field data for file at index '%" PRIu64 "', extra field id '%d', extra field idx '%d': %s\n", idx, eid, eidx, zip_strerror(za)); + return -1; + } + return 0; +} + +static int +get_archive_comment(char *argv[]) { + const char *comment; + int len; + /* get archive comment */ + if ((comment = zip_get_archive_comment(za, &len, 0)) == NULL || len == 0) + printf("No archive comment\n"); + else + printf("Archive comment: %.*s\n", len, encode_filename(comment)); + return 0; +} + +static int +get_archive_flag(char *argv[]) { + int flag = parse_archive_flag(argv[0]); + if (flag < 0) { + fprintf(stderr, "invalid archive flag '%s'\n", argv[0]); + return -1; + } + + printf("%d\n", zip_get_archive_flag(za, flag, 0)); + return 0; +} + +static int +get_extra(char *argv[]) { + zip_flags_t geflags; + zip_uint16_t id, eidx, eflen; + const zip_uint8_t *efdata; + zip_uint64_t idx; + /* get extra field data */ + idx = strtoull(argv[0], NULL, 10); + eidx = (zip_uint16_t)strtoull(argv[1], NULL, 10); + geflags = get_flags(argv[2]); + if ((efdata = zip_file_extra_field_get(za, idx, eidx, &id, &eflen, geflags)) == NULL) { + fprintf(stderr, "can't get extra field data for file at index %" PRIu64 ", extra field %d, flags %u: %s\n", idx, eidx, geflags, zip_strerror(za)); + return -1; + } + printf("Extra field 0x%04x: len %d", id, eflen); + if (eflen > 0) { + printf(", data "); + hexdump(efdata, eflen); + } + printf("\n"); + return 0; +} + +static int +get_extra_by_id(char *argv[]) { + zip_flags_t geflags; + zip_uint16_t eid, eidx, eflen; + const zip_uint8_t *efdata; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + eid = (zip_uint16_t)strtoull(argv[1], NULL, 10); + eidx = (zip_uint16_t)strtoull(argv[2], NULL, 10); + geflags = get_flags(argv[3]); + if ((efdata = zip_file_extra_field_get_by_id(za, idx, eid, eidx, &eflen, geflags)) == NULL) { + fprintf(stderr, "can't get extra field data for file at index %" PRIu64 ", extra field id %d, ef index %d, flags %u: %s\n", idx, eid, eidx, geflags, zip_strerror(za)); + return -1; + } + printf("Extra field 0x%04x: len %d", eid, eflen); + if (eflen > 0) { + printf(", data "); + hexdump(efdata, eflen); + } + printf("\n"); + return 0; +} + +static int +get_file_comment(char *argv[]) { + const char *comment; + zip_uint32_t len; + zip_uint64_t idx; + /* get file comment */ + idx = strtoull(argv[0], NULL, 10); + if ((comment = zip_file_get_comment(za, idx, &len, 0)) == NULL) { + fprintf(stderr, "can't get comment for '%s': %s\n", zip_get_name(za, idx, 0), zip_strerror(za)); + return -1; + } + else if (len == 0) + printf("No comment for '%s'\n", zip_get_name(za, idx, 0)); + else + printf("File comment for '%s': %.*s\n", zip_get_name(za, idx, 0), (int)len, comment); + return 0; +} + +static int +get_num_entries(char *argv[]) { + zip_int64_t count; + zip_flags_t flags; + /* get number of entries in archive */ + flags = get_flags(argv[0]); + count = zip_get_num_entries(za, flags); + printf("%" PRId64 " entr%s in archive\n", count, count == 1 ? "y" : "ies"); + return 0; +} + +static int +name_locate(char *argv[]) { + zip_flags_t flags; + zip_int64_t idx; + flags = get_flags(argv[1]); + + if ((idx = zip_name_locate(za, decode_filename(argv[0]), flags)) < 0) { + fprintf(stderr, "can't find entry with name '%s' using flags '%s'\n", argv[0], argv[1]); + } + else { + printf("name '%s' using flags '%s' found at index %" PRId64 "\n", argv[0], argv[1], idx); + } + + return 0; +} + +struct progress_userdata_s { + double percentage; + double limit; +}; + +struct progress_userdata_s progress_userdata; + +static void +progress_callback(zip_t *archive, double percentage, void *ud) { + printf("%.1f%% done\n", percentage * 100); + progress_userdata.percentage = percentage; +} + +static int +print_progress(char *argv[]) { + zip_register_progress_callback_with_state(za, 0.001, progress_callback, NULL, NULL); + return 0; +} + +static int +zrename(char *argv[]) { + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + if (zip_file_rename(za, idx, decode_filename(argv[1]), 0) < 0) { + fprintf(stderr, "can't rename file at index '%" PRIu64 "' to '%s': %s\n", idx, argv[1], zip_strerror(za)); + return -1; + } + return 0; +} + +static int +replace_file_contents(char *argv[]) { + /* replace file contents with data from command line */ + const char *content; + zip_source_t *s; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + content = argv[1]; + if ((s = zip_source_buffer(za, content, strlen(content), 0)) == NULL || zip_file_replace(za, idx, s, 0) < 0) { + zip_source_free(s); + fprintf(stderr, "error replacing file data: %s\n", zip_strerror(za)); + return -1; + } + return 0; +} + +static int +set_extra(char *argv[]) { + zip_flags_t geflags; + zip_uint16_t eid, eidx; + const zip_uint8_t *efdata; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + eid = (zip_uint16_t)strtoull(argv[1], NULL, 10); + eidx = (zip_uint16_t)strtoull(argv[2], NULL, 10); + geflags = get_flags(argv[3]); + efdata = (zip_uint8_t *)argv[4]; + if ((zip_file_extra_field_set(za, idx, eid, eidx, efdata, (zip_uint16_t)strlen((const char *)efdata), geflags)) < 0) { + fprintf(stderr, "can't set extra field data for file at index '%" PRIu64 "', extra field id '%d', index '%d': %s\n", idx, eid, eidx, zip_strerror(za)); + return -1; + } + return 0; +} + +static int +set_archive_comment(char *argv[]) { + if (zip_set_archive_comment(za, argv[0], (zip_uint16_t)strlen(argv[0])) < 0) { + fprintf(stderr, "can't set archive comment to '%s': %s\n", argv[0], zip_strerror(za)); + return -1; + } + return 0; +} + +static int +set_archive_flag(char *argv[]) { + int flag = parse_archive_flag(argv[0]); + if (flag < 0) { + fprintf(stderr, "invalid archive flag '%s'\n", argv[0]); + return -1; + } + + int value = strcasecmp(argv[1], "1") == 0 || strcasecmp(argv[1], "true") == 0 || strcasecmp(argv[1], "yes") == 0; + + if (zip_set_archive_flag(za, flag, value) < 0) { + fprintf(stderr, "can't set archive flag '%s' to %d: %s\n", argv[0], value, zip_strerror(za)); + return -1; + } + return 0; +} + + +static int +set_file_comment(char *argv[]) { + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + if (zip_file_set_comment(za, idx, argv[1], (zip_uint16_t)strlen(argv[1]), 0) < 0) { + fprintf(stderr, "can't set file comment at index '%" PRIu64 "' to '%s': %s\n", idx, argv[1], zip_strerror(za)); + return -1; + } + return 0; +} + +static int +set_file_compression(char *argv[]) { + zip_int32_t method; + zip_uint32_t flags; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + method = get_compression_method(argv[1]); + flags = (zip_uint32_t)strtoull(argv[2], NULL, 10); + if (zip_set_file_compression(za, idx, method, flags) < 0) { + fprintf(stderr, "can't set file compression method at index '%" PRIu64 "' to '%s', flags '%" PRIu32 "': %s\n", idx, argv[1], flags, zip_strerror(za)); + return -1; + } + return 0; +} + +static int +set_file_encryption(char *argv[]) { + zip_uint16_t method; + zip_uint64_t idx; + char *password; + idx = strtoull(argv[0], NULL, 10); + method = get_encryption_method(argv[1]); + password = argv[2]; + if (strlen(password) == 0) { + password = NULL; + } + if (zip_file_set_encryption(za, idx, method, password) < 0) { + fprintf(stderr, "can't set file encryption method at index '%" PRIu64 "' to '%s': %s\n", idx, argv[1], zip_strerror(za)); + return -1; + } + return 0; +} + +static int +set_file_dostime(char *argv[]) { + /* set file last modification time (mtime) directly */ + zip_uint16_t dostime, dosdate; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + dostime = (zip_uint16_t)strtoull(argv[1], NULL, 10); + dosdate = (zip_uint16_t)strtoull(argv[2], NULL, 10); + if (zip_file_set_dostime(za, idx, dostime, dosdate, 0) < 0) { + fprintf(stderr, "can't set file dostime at index '%" PRIu64 "' to '%d'/'%d': %s\n", idx, (int)dostime, (int)dosdate, zip_strerror(za)); + return -1; + } + return 0; +} + +static int +set_file_mtime(char *argv[]) { + /* set file last modification time (mtime) */ + time_t mtime; + zip_uint64_t idx; + idx = strtoull(argv[0], NULL, 10); + mtime = (time_t)strtoull(argv[1], NULL, 10); + if (zip_file_set_mtime(za, idx, mtime, 0) < 0) { + fprintf(stderr, "can't set file mtime at index '%" PRIu64 "' to '%lld': %s\n", idx, (long long)mtime, zip_strerror(za)); + return -1; + } + return 0; +} + +static int +set_file_mtime_all(char *argv[]) { + /* set last modification time (mtime) for all files */ + time_t mtime; + zip_int64_t num_entries; + zip_uint64_t idx; + mtime = (time_t)strtoull(argv[0], NULL, 10); + + if ((num_entries = zip_get_num_entries(za, 0)) < 0) { + fprintf(stderr, "can't get number of entries: %s\n", zip_strerror(za)); + return -1; + } + for (idx = 0; idx < (zip_uint64_t)num_entries; idx++) { + if (zip_file_set_mtime(za, idx, mtime, 0) < 0) { + fprintf(stderr, "can't set file mtime at index '%" PRIu64 "' to '%lld': %s\n", idx, (long long)mtime, zip_strerror(za)); + return -1; + } + } + return 0; +} + +static int +set_password(char *argv[]) { + /* set default password */ + if (zip_set_default_password(za, argv[0]) < 0) { + fprintf(stderr, "can't set default password to '%s'\n", argv[0]); + return -1; + } + return 0; +} + +static int +zstat(char *argv[]) { + zip_uint64_t idx; + char buf[100]; + struct zip_stat sb; + idx = strtoull(argv[0], NULL, 10); + + if (zip_stat_index(za, idx, stat_flags, &sb) < 0) { + fprintf(stderr, "zip_stat_index failed on '%" PRIu64 "' failed: %s\n", idx, zip_strerror(za)); + return -1; + } + + if (sb.valid & ZIP_STAT_NAME) + printf("name: '%s'\n", encode_filename(sb.name)); + if (sb.valid & ZIP_STAT_INDEX) + printf("index: '%" PRIu64 "'\n", sb.index); + if (sb.valid & ZIP_STAT_SIZE) + printf("size: '%" PRIu64 "'\n", sb.size); + if (sb.valid & ZIP_STAT_COMP_SIZE) + printf("compressed size: '%" PRIu64 "'\n", sb.comp_size); + if (sb.valid & ZIP_STAT_MTIME) { + struct tm *tpm; + struct tm tm; + tpm = zip_localtime(&sb.mtime, &tm); + if (tpm == NULL) { + printf("mtime: \n"); + } + else { + strftime(buf, sizeof(buf), "%a %b %d %Y %H:%M:%S", tpm); + printf("mtime: '%s'\n", buf); + } + } + if (sb.valid & ZIP_STAT_CRC) + printf("crc: '%0x'\n", sb.crc); + if (sb.valid & ZIP_STAT_COMP_METHOD) + printf("compression method: '%d'\n", sb.comp_method); + if (sb.valid & ZIP_STAT_ENCRYPTION_METHOD) + printf("encryption method: '%d'\n", sb.encryption_method); + if (sb.valid & ZIP_STAT_FLAGS) + printf("flags: '%ld'\n", (long)sb.flags); + printf("\n"); + + return 0; +} + +static int parse_archive_flag(const char* arg) { + if (strcasecmp(arg, "rdonly") == 0) { + return ZIP_AFL_RDONLY; + } + else if (strcasecmp(arg, "is-torrentzip") == 0) { + return ZIP_AFL_IS_TORRENTZIP; + } + else if (strcasecmp(arg, "want-torrentzip") == 0) { + return ZIP_AFL_WANT_TORRENTZIP; + } + else if (strcasecmp(arg, "create-or-keep-file-for-empty-archive") == 0) { + return ZIP_AFL_CREATE_OR_KEEP_FILE_FOR_EMPTY_ARCHIVE; + } + return -1; +} + +static zip_flags_t +get_flags(const char *arg) { + zip_flags_t flags = 0; + if (strchr(arg, 'C') != NULL) + flags |= ZIP_FL_NOCASE; + if (strchr(arg, 'c') != NULL) + flags |= ZIP_FL_CENTRAL; + if (strchr(arg, 'd') != NULL) + flags |= ZIP_FL_NODIR; + if (strchr(arg, 'l') != NULL) + flags |= ZIP_FL_LOCAL; + if (strchr(arg, 'u') != NULL) + flags |= ZIP_FL_UNCHANGED; + if (strchr(arg, '8') != NULL) + flags |= ZIP_FL_ENC_UTF_8; + if (strchr(arg, '4') != NULL) + flags |= ZIP_FL_ENC_CP437; + if (strchr(arg, 'r') != NULL) + flags |= ZIP_FL_ENC_RAW; + if (strchr(arg, 's') != NULL) + flags |= ZIP_FL_ENC_STRICT; + return flags; +} + +static zip_int32_t +get_compression_method(const char *arg) { + if (strcasecmp(arg, "default") == 0) + return ZIP_CM_DEFAULT; + else if (strcasecmp(arg, "store") == 0) + return ZIP_CM_STORE; + else if (strcasecmp(arg, "deflate") == 0) + return ZIP_CM_DEFLATE; +#if defined(HAVE_LIBBZ2) + else if (strcasecmp(arg, "bzip2") == 0) + return ZIP_CM_BZIP2; +#endif +#if defined(HAVE_LIBLZMA) + /* Disabled - because 7z isn't able to unpack ZIP+LZMA ZIP+LZMA2 + archives made this way - and vice versa. + + else if (strcasecmp(arg, "lzma2") == 0) + return ZIP_CM_LZMA2; + */ + else if (strcasecmp(arg, "lzma") == 0) + return ZIP_CM_LZMA; + else if (strcasecmp(arg, "xz") == 0) + return ZIP_CM_XZ; +#endif +#if defined(HAVE_LIBZSTD) + else if (strcasecmp(arg, "zstd") == 0) + return ZIP_CM_ZSTD; + +#endif + else if (strcasecmp(arg, "unknown") == 0) + return 100; + return 0; /* TODO: error handling */ +} + +static zip_uint16_t +get_encryption_method(const char *arg) { + if (strcasecmp(arg, "none") == 0) + return ZIP_EM_NONE; + else if (strcasecmp(arg, "PKWARE") == 0) + return ZIP_EM_TRAD_PKWARE; + else if (strcasecmp(arg, "AES-128") == 0) + return ZIP_EM_AES_128; + else if (strcasecmp(arg, "AES-192") == 0) + return ZIP_EM_AES_192; + else if (strcasecmp(arg, "AES-256") == 0) + return ZIP_EM_AES_256; + else if (strcasecmp(arg, "unknown") == 0) + return 100; + return (zip_uint16_t)-1; /* TODO: error handling */ +} + +static void +hexdump(const zip_uint8_t *data, zip_uint16_t len) { + zip_uint16_t i; + + if (len <= 0) + return; + + printf("0x"); + + for (i = 0; i < len; i++) + printf("%02x", data[i]); +} + + +static zip_t * +read_from_file(const char *archive, int flags, zip_error_t *error, zip_uint64_t offset, zip_uint64_t length) { + zip_t *zaa; + zip_source_t *source; + int err; + + if (offset == 0 && length == 0) { + if (strcmp(archive, "/dev/stdin") == 0) { + zaa = zip_fdopen(STDIN_FILENO, flags & ~ZIP_CREATE, &err); + } + else { + zaa = zip_open(archive, flags, &err); + } + if (zaa == NULL) { + zip_error_set(error, err, errno); + return NULL; + } + } + else { + if (length > ZIP_INT64_MAX) { + zip_error_set(error, ZIP_ER_INVAL, 0); + return NULL; + } + if ((source = zip_source_file_create(archive, offset, (zip_int64_t)length, error)) == NULL || (zaa = zip_open_from_source(source, flags, error)) == NULL) { + zip_source_free(source); + return NULL; + } + } + + return zaa; +} + +dispatch_table_t dispatch_table[] = {{"add", 2, "name content", "add file called name using content", add}, + {"add_dir", 1, "name", "add directory", add_dir}, + {"add_file", 4, "name file_to_add offset len", "add file to archive, len bytes starting from offset", add_file}, + {"add_from_zip", 5, "name archivename index offset len", "add file from another archive, len bytes starting from offset", add_from_zip}, + {"cat", 1, "index", "output file contents to stdout", cat}, + {"cat_partial", 3, "index start length", "output partial file contents to stdout", cat_partial}, + {"count_extra", 2, "index flags", "show number of extra fields for archive entry", count_extra}, + {"count_extra_by_id", 3, "index extra_id flags", "show number of extra fields of type extra_id for archive entry", count_extra_by_id}, + {"delete", 1, "index", "remove entry", delete}, + {"delete_extra", 3, "index extra_idx flags", "remove extra field", delete_extra}, + {"delete_extra_by_id", 4, "index extra_id extra_index flags", "remove extra field of type extra_id", delete_extra_by_id}, + {"get_archive_comment", 0, "", "show archive comment", get_archive_comment}, + {"get_archive_flag", 1, "flag", "show archive flag", get_archive_flag}, + {"get_extra", 3, "index extra_index flags", "show extra field", get_extra}, + {"get_extra_by_id", 4, "index extra_id extra_index flags", "show extra field of type extra_id", get_extra_by_id}, + {"get_file_comment", 1, "index", "get file comment", get_file_comment}, + {"get_num_entries", 1, "flags", "get number of entries in archive", get_num_entries}, + {"name_locate", 2, "name flags", "find entry in archive", name_locate}, + {"print_progress", 0, "", "print progress during zip_close()", print_progress}, + {"rename", 2, "index name", "rename entry", zrename}, + {"replace_file_contents", 2, "index data", "replace entry with data", replace_file_contents}, + {"set_archive_comment", 1, "comment", "set archive comment", set_archive_comment}, + {"set_archive_flag", 2, "flag", "set archive flag", set_archive_flag}, + {"set_extra", 5, "index extra_id extra_index flags value", "set extra field", set_extra}, + {"set_file_comment", 2, "index comment", "set file comment", set_file_comment}, + {"set_file_compression", 3, "index method compression_flags", "set file compression method", set_file_compression}, + {"set_file_dostime", 3, "index time date", "set file modification time and date (DOS format)", set_file_dostime}, + {"set_file_encryption", 3, "index method password", "set file encryption method", set_file_encryption}, + {"set_file_mtime", 2, "index timestamp", "set file modification time", set_file_mtime}, + {"set_file_mtime_all", 1, "timestamp", "set file modification time for all files", set_file_mtime_all}, + {"set_password", 1, "password", "set default password for encryption", set_password}, + {"stat", 1, "index", "print information about entry", zstat} +#ifdef DISPATCH_REGRESS + , + DISPATCH_REGRESS +#endif +}; + +static int +dispatch(int argc, char *argv[]) { + unsigned int i; + for (i = 0; i < sizeof(dispatch_table) / sizeof(dispatch_table_t); i++) { + if (strcmp(dispatch_table[i].cmdline_name, argv[0]) == 0) { + argc--; + argv++; + /* 1 for the command, argument_count for the arguments */ + if (argc < dispatch_table[i].argument_count) { + fprintf(stderr, "not enough arguments for command '%s': %d available, %d needed\n", dispatch_table[i].cmdline_name, argc, dispatch_table[i].argument_count); + return -1; + } + if (dispatch_table[i].function(argv) == 0) + return 1 + dispatch_table[i].argument_count; + return -1; + } + } + + fprintf(stderr, "unknown command '%s'\n", argv[0]); + return -1; +} + + +static void +usage(const char *progname, const char *reason) { + unsigned int i; + FILE *out; + if (reason == NULL) + out = stdout; + else + out = stderr; + fprintf(out, "usage: %s [-ceghnrst]" USAGE_REGRESS " [-l len] [-o offset] archive command1 [args] [command2 [args] ...]\n", progname); + if (reason != NULL) { + fprintf(out, "%s\n", reason); + exit(1); + } + + fprintf(out, "\nSupported options are:\n" + "\t-c\t\tcheck consistency\n" + "\t-e\t\terror if archive already exists (only useful with -n)\n" +#ifdef FOR_REGRESS + "\t-F size\t\tfragment size for in memory archive\n" +#endif + "\t-g\t\tguess file name encoding (for stat)\n" +#ifdef FOR_REGRESS + "\t-H\t\twrite files with holes compactly\n" +#endif + "\t-h\t\tdisplay this usage\n" + "\t-l len\t\tonly use len bytes of file\n" +#ifdef FOR_REGRESS + "\t-m\t\tread archive into memory, and modify there; write out at end\n" +#endif + "\t-n\t\tcreate archive if it doesn't exist\n" + "\t-o offset\tstart reading file at offset\n" + "\t-r\t\tprint raw file name encoding without translation (for stat)\n" + "\t-s\t\tfollow file name convention strictly (for stat)\n" + "\t-t\t\tdisregard current archive contents, if any\n"); + fprintf(out, "\nSupported commands and arguments are:\n"); + for (i = 0; i < sizeof(dispatch_table) / sizeof(dispatch_table_t); i++) { + fprintf(out, "\t%s %s\n\t %s\n\n", dispatch_table[i].cmdline_name, dispatch_table[i].arg_names, dispatch_table[i].description); + } + fprintf(out, "\nSupported flags are:\n" + "\t0\t(no flags)\n" + "\t4\tZIP_FL_ENC_CP437\n" + "\t8\tZIP_FL_ENC_UTF_8\n" + "\tC\tZIP_FL_NOCASE\n" + "\tc\tZIP_FL_CENTRAL\n" + "\td\tZIP_FL_NODIR\n" + "\tl\tZIP_FL_LOCAL\n" + "\tr\tZIP_FL_ENC_RAW\n" + "\ts\tZIP_FL_ENC_STRICT\n" + "\tu\tZIP_FL_UNCHANGED\n"); + fprintf(out, "\nSupported archive flags are:\n" + "\tcreate-or-keep-empty-file-for-archive\n" + "\tis-torrentzip\n" + "\trdonly\n" + "\twant-torrentzip\n"); + fprintf(out, "\nSupported compression methods are:\n" + "\tdefault\n"); + if (zip_compression_method_supported(ZIP_CM_BZIP2, 1)) { + fprintf(out, "\tbzip2\n"); + } + fprintf(out, "\tdeflate\n" + "\tstore\n"); + if (zip_compression_method_supported(ZIP_CM_XZ, 1)) { + fprintf(out, "\txz\n"); + } + if (zip_compression_method_supported(ZIP_CM_ZSTD, 1)) { + fprintf(out, "\tzstd\n"); + } + fprintf(out, "\nSupported encryption methods are:\n" + "\tnone\n"); + if (zip_encryption_method_supported(ZIP_EM_AES_128, 1)) { + fprintf(out, "\tAES-128\n"); + } + if (zip_encryption_method_supported(ZIP_EM_AES_192, 1)) { + fprintf(out, "\tAES-192\n"); + } + if (zip_encryption_method_supported(ZIP_EM_AES_256, 1)) { + fprintf(out, "\tAES-256\n"); + } + fprintf(out, "\tPKWARE\n"); + fprintf(out, "\nThe index is zero-based.\n"); + exit(0); +} + +#ifndef FOR_REGRESS +#define ziptool_open read_from_file +int +ziptool_post_close(const char *archive) { + return 0; +} +#endif + +int +main(int argc, char *argv[]) { + const char *archive; + unsigned int i; + int c, arg, err, flags; + const char *prg; + zip_uint64_t len = 0, offset = 0; + zip_error_t error; + + flags = 0; + prg = argv[0]; + + while ((c = getopt(argc, argv, "ceghl:no:rst" OPTIONS_REGRESS)) != -1) { + switch (c) { + case 'c': + flags |= ZIP_CHECKCONS; + break; + case 'e': + flags |= ZIP_EXCL; + break; + case 'g': + stat_flags = ZIP_FL_ENC_GUESS; + break; + case 'h': + usage(prg, NULL); + break; + case 'l': + len = strtoull(optarg, NULL, 10); + break; + case 'n': + flags |= ZIP_CREATE; + break; + case 'o': + offset = strtoull(optarg, NULL, 10); + break; + case 'r': + stat_flags = ZIP_FL_ENC_RAW; + break; + case 's': + stat_flags = ZIP_FL_ENC_STRICT; + break; + case 't': + flags |= ZIP_TRUNCATE; + break; +#ifdef GETOPT_REGRESS + GETOPT_REGRESS +#endif + + default: { + char reason[128]; + snprintf(reason, sizeof(reason), "invalid option -%c", optopt); + usage(prg, reason); + } + } + } + + if (optind >= argc - 1) + usage(prg, "too few arguments"); + + arg = optind; + + archive = argv[arg++]; + + if (flags == 0) + flags = ZIP_CREATE; + + zip_error_init(&error); + za = ziptool_open(archive, flags, &error, offset, len); + if (za == NULL) { + fprintf(stderr, "can't open zip archive '%s': %s\n", archive, zip_error_strerror(&error)); + zip_error_fini(&error); + return 1; + } + zip_error_fini(&error); + +#ifdef REGRESS_PREPARE_ARGS + REGRESS_PREPARE_ARGS +#endif + + err = 0; + while (arg < argc) { + int ret; + ret = dispatch(argc - arg, argv + arg); + if (ret > 0) { + arg += ret; + } + else { + err = 1; + break; + } + } + +#ifdef PRECLOSE_REGRESS + PRECLOSE_REGRESS; +#endif + + if (zip_close(za) == -1) { + fprintf(stderr, "can't close zip archive '%s': %s\n", archive, zip_strerror(za)); + return 1; + } + if (ziptool_post_close(archive) < 0) { + err = 1; + } + + for (i = 0; i < z_in_count; i++) { + if (zip_close(z_in[i]) < 0) { + err = 1; + } + } + + return err; +} + +#define BIN2HEX(n) ((n) >= 10 ? (n) + 'a' - 10 : (n) + '0') +#define HEX2BIN(c) (((c) >= '0' && (c) <= '9') ? (c) - '0' : ((c) >= 'A' && (c) <= 'F') ? (c) - 'A' + 10 : (c) - 'a' + 10) + +#define FILENAME_BUFFER_LENGTH 8192 +static char filename_buffer[FILENAME_BUFFER_LENGTH + 1]; + +static const char* encode_filename(const char* name) { + char *t = filename_buffer; + + if (!hex_encoded_filenames) { + const char* s = name; + if (strlen(name) > FILENAME_BUFFER_LENGTH) { + // TODO: error message + exit(1); + } + while (*s != '\0') { + if (s[0] == '\r' && s[1] == '\n') { + s++; + continue; + } + *(t++) = *(s++); + } + } + else { + const unsigned char *s = (const unsigned char *)name; + if (strlen(name) > FILENAME_BUFFER_LENGTH / 2) { + // TODO: error message + exit(1); + } + while (*s != '\0') { + *(t++) = BIN2HEX(*s >> 4); + *(t++) = BIN2HEX(*s & 0xf); + s += 1; + } + } + *t = '\0'; + return filename_buffer; +} + +static const char* decode_filename(const char* name) { + if (!hex_encoded_filenames) { + return name; + } + + if (strlen(name) > FILENAME_BUFFER_LENGTH * 2) { + // TODO: error message + exit(1); + } + // TODO: check that strlen(name) % 2 == 0 + // TODO: check with strspn that s is all hex digits + + unsigned char *t = (unsigned char*)filename_buffer; + const char *s = name; + while (*s != '\0') { + *(t++) = (HEX2BIN(s[0]) << 4) | HEX2BIN(s[1]); + s += 2; + } + *t = '\0'; + + return filename_buffer; +} diff --git a/core/deps/libzip/vstudio/readme.txt b/core/deps/libzip/vstudio/readme.txt deleted file mode 100644 index 72b7546a5..000000000 --- a/core/deps/libzip/vstudio/readme.txt +++ /dev/null @@ -1,77 +0,0 @@ -Building libzip with Microsoft Visual C++ -========================================= -The script vsbuild.cmd can be used to build (and optionally test) libzip using -Microsoft Visual C++. - -Prerequisites -------------- -Windows Server 2003 or Windows Vista or later (tested with Windows 7 x64) -Microsoft Visual Studio (tested with VS2013 Premium) -CMake (tested with version 3.3.1) -Source code for zlib (tested with version 1.2.8) - -To run tests, you will also need: -Perl interpreter (tested with ActiveState ActivePerl 5.20.2 64-bit) -CPAN module IPC::Cmd (comes with ActivePerl) - -You'll also need to make sure all of the above (specifically: msbuild.exe, -cmake.exe and perl.exe) are in your PATH at the time of running the script. - -Unpacking zlib --------------- -Because libzip depends on zlib, the vsbuild.cmd script takes care of building -zlib for you as well. Download the source code from http://zlib.org/ and extract -all the files and subdirectories from the "zlib-x.y.z" directory into -vstudio/zlib. - -Building libzip with Visual Studio 2013 for the impatient ---------------------------------------------------------- -1. Make sure all the prerequisites (see above) are installed and in your PATH. -2. Don't forget to unpack zlib (see "Unpacking zlib" above). -3. Open a Visual Studio Command Prompt in the "vstudio" directory (the directory - containing this file). -4. Enter the following command for a 32-bit x86 build: - vsbuild build "Visual Studio 12" v120 - or for a 64-bit x64 build: - vsbuild build "Visual Studio 12 Win64" v120 - -If you'd like to run the tests as well, use one of the following commands: -vsbuild build+test "Visual Studio 12" v120 -vsbuild build+test "Visual Studio 12 Win64" v120 - -Building libzip ---------------- -The script vsbuild.cmd has three modes of operation, with the following syntax: -vsbuild clean -vsbuild build -vsbuild build+test - -"vsbuild clean" deletes all output and intermediate files and directories -generated by the build process. - -"vsbuild build" just builds zlib and libzip and leaves the DLL files in the -following locations: -vstudio/zlib/installed/bin/zlib.dll -vstudio/zlib/installed/bin/zlibd.dll -build/lib/Debug/zip.dll -build/lib/Release/zip.dll - -"vsbuild build+test" builds zlib and libzip as above, then runs the libzip -regression test suite. - -The "build" and "build+test" commands require the following parameters: - -: The CMake generator to use for project files. This identifies the -version of Visual Studio you're using, and also allows you to specify whether -the 32-bit or 64-bit of the libraries should be built. -I've tested "Visual Studio 12" (VS2013 x86) and "Visual Studio 12 Win64" -(VS2013 x64) but other versions should work as well. Run "cmake --help" for a -list of generators. - -: Specifies the platform toolset to use. Normally, this will match the -version of Visual Studio (e.g. "v120" for Visual Studio 2013 aka Visual Studio -12). See the Visual Studio documentation for more information about platform -toolsets. - -Andrew Molyneux -andrew@molyneuxfamily.co.uk \ No newline at end of file diff --git a/core/deps/libzip/vstudio/vsbuild.cmd b/core/deps/libzip/vstudio/vsbuild.cmd deleted file mode 100644 index 9b7d8bd7e..000000000 --- a/core/deps/libzip/vstudio/vsbuild.cmd +++ /dev/null @@ -1,186 +0,0 @@ -@echo off -setlocal enableextensions enabledelayedexpansion - -rem --------------------------------------------------------------------------- -rem Check that required commands are in the PATH. -rem --------------------------------------------------------------------------- -set CHECK_CMD=cmake.exe -set CHECK_DESC=CMake -call :check_installed -set CHECK_CMD=msbuild.exe -set CHECK_DESC=MSBuild -call :check_installed - -rem --------------------------------------------------------------------------- -rem Parse command-line arguments. -rem %1: Command ("build" or "clean") -rem %2: Generator (e.g. "Visual Studio 12" for VS2013) -rem %3: Platform toolset (e.g. "v120_xp" for VS2013 toolset for Windows XP) -rem --------------------------------------------------------------------------- -if "%1"=="clean" ( - echo Cleaning - if exist zlib\installed rmdir /s /q zlib\installed - if errorlevel 1 goto exit_failure - if exist zlib\build rmdir /s /q zlib\build - if errorlevel 1 goto exit_failure - if exist ..\build rmdir /s /q ..\build - if errorlevel 1 goto exit_failure - if exist ..\regress\bigzero.zip del ..\regress\bigzero.zip - if errorlevel 1 goto exit_failure - if exist ..\regress\manyfiles.zip del ..\regress\manyfiles.zip - if errorlevel 1 goto exit_failure - if exist ..\regress\manyfiles-133000.zip del ..\regress\manyfiles-133000.zip - if errorlevel 1 goto exit_failure - if exist ..\regress\manyfiles-65536.zip del ..\regress\manyfiles-65536.zip - if errorlevel 1 goto exit_failure - if exist ..\regress\manyfiles-zip64-modulo.zip del ..\regress\manyfiles-zip64-modulo.zip - if errorlevel 1 goto exit_failure - if exist ..\regress\manyfiles-zip64.zip del ..\regress\manyfiles-zip64.zip - if errorlevel 1 goto exit_failure - if exist ..\regress\manyfiles-fewer.zip del ..\regress\manyfiles-fewer.zip - if errorlevel 1 goto exit_failure - if exist ..\regress\manyfiles-more.zip del ..\regress\manyfiles-more.zip - if errorlevel 1 goto exit_failure - echo Done - exit /b 0 -) else if "%1"=="build" ( - set CMAKE_GENERATOR=%2 - set CMAKE_TOOLSET=%3 - set LIBZIP_RUN_TESTS=false -) else if "%1"=="build+test" ( - set CMAKE_GENERATOR=%2 - set CMAKE_TOOLSET=%3 - set LIBZIP_RUN_TESTS=true -) else ( - echo Invalid command "%1" - exit /b 1 -) - -rem --------------------------------------------------------------------------- -rem If we're running tests, we'll also need a Perl interpreter. -rem --------------------------------------------------------------------------- -if "%LIBZIP_RUN_TESTS%"=="true" ( - set CHECK_CMD=perl.exe - set CHECK_DESC=a Perl interpreter (to run tests) - call :check_installed -) - -rem --------------------------------------------------------------------------- -rem Configure and build zlib. -rem --------------------------------------------------------------------------- -pushd zlib -for /f %%p in (".\installed") do set ZLIB_INSTALL_PATH=%%~fp -echo zlib will be "installed" to %ZLIB_INSTALL_PATH% -if not exist build ( - mkdir build - if errorlevel 1 popd & goto exit_failure -) -cd build -if errorlevel 1 popd & goto exit_failure -echo Configuring zlib -cmake .. -G %CMAKE_GENERATOR% -T %CMAKE_TOOLSET% -DCMAKE_INSTALL_PREFIX="%ZLIB_INSTALL_PATH%" -if errorlevel 1 popd & goto exit_failure -echo Building zlib -msbuild /P:Configuration=Debug INSTALL.vcxproj -if errorlevel 1 popd & goto exit_failure -msbuild /P:Configuration=Release INSTALL.vcxproj -if errorlevel 1 popd & goto exit_failure -popd - -rem --------------------------------------------------------------------------- -rem Prepare the build directory and run CMake to configure the project. -rem --------------------------------------------------------------------------- -pushd .. -if not exist build ( - echo Creating build directory - mkdir build - if errorlevel 1 popd & goto exit_failure -) -cd build -if errorlevel 1 popd & goto exit_failure -cmake .. -G %CMAKE_GENERATOR% -T %CMAKE_TOOLSET% -DCMAKE_PREFIX_PATH="%ZLIB_INSTALL_PATH%" -if errorlevel 1 popd & goto exit_failure -goto :EOF - -rem --------------------------------------------------------------------------- -rem Build libzip. -rem --------------------------------------------------------------------------- -msbuild /P:Configuration=Debug ALL_BUILD.vcxproj -if errorlevel 1 popd & goto exit_failure -msbuild /P:Configuration=Release ALL_BUILD.vcxproj -if errorlevel 1 popd & goto exit_failure -popd - -rem --------------------------------------------------------------------------- -rem Copy DLLs so zipcmp/zipmerge can run. -rem --------------------------------------------------------------------------- -echo Copying DLLs -copy zlib\installed\bin\zlibd.dll ..\build\src\Debug -if errorlevel 1 goto exit_failure -copy zlib\installed\bin\zlib.dll ..\build\src\Release -if errorlevel 1 goto exit_failure -copy ..\build\lib\Release\zip.dll ..\build\src\Release -if errorlevel 1 goto exit_failure -copy ..\build\lib\Debug\zip.dll ..\build\src\Debug -if errorlevel 1 goto exit_failure - -rem --------------------------------------------------------------------------- -rem Run the tests, if required. -rem --------------------------------------------------------------------------- -if "%LIBZIP_RUN_TESTS%"=="true" ( - echo Copying libraries for tests - pushd ..\build\regress - copy ..\..\vstudio\zlib\installed\bin\zlib.dll . - if errorlevel 1 popd & goto exit_failure - copy ..\lib\Release\zip.dll . - if errorlevel 1 popd & goto exit_failure - copy Release\*.exe . - if errorlevel 1 popd & goto exit_failure - copy ..\src\Release\*.exe . - if errorlevel 1 popd & goto exit_failure - echo Extracting test files - if not exist ..\..\regress\bigzero.zip ziptool ..\..\regress\bigzero-zip.zip cat 0 > ..\..\regress\bigzero.zip - if errorlevel 1 popd & goto exit_failure - if not exist ..\..\regress\manyfiles.zip ziptool ..\..\regress\manyfiles-zip.zip cat 0 > ..\..\regress\manyfiles.zip - if errorlevel 1 popd & goto exit_failure - if not exist ..\..\regress\manyfiles-133000.zip ziptool ..\..\regress\manyfiles-zip.zip cat 1 > ..\..\regress\manyfiles-133000.zip - if errorlevel 1 popd & goto exit_failure - if not exist ..\..\regress\manyfiles-65536.zip ziptool ..\..\regress\manyfiles-zip.zip cat 2 > ..\..\regress\manyfiles-65536.zip - if errorlevel 1 popd & goto exit_failure - if not exist ..\..\regress\manyfiles-zip64-modulo.zip ziptool ..\..\regress\manyfiles-zip.zip cat 3 > ..\..\regress\manyfiles-zip64-modulo.zip - if errorlevel 1 popd & goto exit_failure - if not exist ..\..\regress\manyfiles-zip64.zip ziptool ..\..\regress\manyfiles-zip.zip cat 4 > ..\..\regress\manyfiles-zip64.zip - if errorlevel 1 popd & goto exit_failure - if not exist ..\..\regress\manyfiles-fewer.zip ziptool ..\..\regress\manyfiles-zip.zip cat 5 > ..\..\regress\manyfiles-fewer.zip - if errorlevel 1 popd & goto exit_failure - if not exist ..\..\regress\manyfiles-more.zip ziptool ..\..\regress\manyfiles-zip.zip cat 6 > ..\..\regress\manyfiles-more.zip - if errorlevel 1 popd & goto exit_failure - echo Generating runtest script - for /f %%p in ("..\..\regress") do set ABS_SRCDIR=%%~fp - set ABS_SRCDIR=!ABS_SRCDIR:\=\\! - perl -p -e "s/@[s]rcdir@/..\\..\\regress/g;s/@[a]bs_srcdir@/!ABS_SRCDIR!/g;s|../../src/zipcmp|..\\..\\src\\Release\\zipcmp|g;" ..\..\regress\runtest.in > runtest - if errorlevel 1 popd & goto exit_failure - echo Running tests - ctest - popd - if errorlevel 1 goto exit_failure -) - -goto :EOF - -:check_installed -where %CHECK_CMD% > nul 2>&1 -if "%errorlevel%"=="9009" ( - echo This build script requires where.exe. If running on Windows XP or - echo earlier, this can be found in the Windows Resource Kit. - exit /b 1 -) -if errorlevel 1 ( - echo Please make sure that %CHECK_DESC% is installed and in your PATH. - exit /b 1 -) -goto :EOF - -:exit_failure -echo Build failed. -exit /b 1 diff --git a/core/deps/libzip/vstudio/zlib/unpack_zlib_here.txt b/core/deps/libzip/vstudio/zlib/unpack_zlib_here.txt deleted file mode 100644 index f4e51a6e2..000000000 --- a/core/deps/libzip/vstudio/zlib/unpack_zlib_here.txt +++ /dev/null @@ -1 +0,0 @@ -Unpack zlib source archive into this directory. \ No newline at end of file From 8d2bcdf35e0dec608c6478b8da68c466ddd0b2b0 Mon Sep 17 00:00:00 2001 From: scribam Date: Thu, 15 Feb 2024 18:03:03 +0100 Subject: [PATCH 10/86] deps: update libchdr --- .gitmodules | 3 ++- CMakeLists.txt | 6 +++--- core/deps/libchdr | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitmodules b/.gitmodules index f29ba3db2..c4747d15b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,8 @@ url = https://github.com/libsdl-org/SDL.git [submodule "core/deps/libchdr"] path = core/deps/libchdr - url = https://github.com/flyinghead/libchdr.git + url = https://github.com/rtissera/libchdr.git + ignore = dirty [submodule "core/deps/luabridge"] path = core/deps/luabridge url = https://github.com/vinniefalco/LuaBridge.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 95c175250..a32240fd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -453,15 +453,15 @@ target_link_libraries(${PROJECT_NAME} PRIVATE chdr-static) target_include_directories(${PROJECT_NAME} PRIVATE core/deps/libchdr/include) if(NOT WITH_SYSTEM_ZLIB) - set(ZLIB_RELATIVE_PATH "core/deps/libchdr/deps/zlib-1.2.12") + set(ZLIB_RELATIVE_PATH "core/deps/libchdr/deps/zlib-1.3.1") target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/${ZLIB_RELATIVE_PATH}" "${CMAKE_CURRENT_BINARY_DIR}/${ZLIB_RELATIVE_PATH}") # help libzip find the package set(ZLIB_FOUND TRUE) set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${ZLIB_RELATIVE_PATH}" "${CMAKE_CURRENT_BINARY_DIR}/${ZLIB_RELATIVE_PATH}") cmake_policy(SET CMP0026 OLD) - get_target_property(ZLIB_LIBRARY_RELEASE zlib LOCATION) - get_target_property(ZLIB_LIBRARY_DEBUG zlib LOCATION_Debug) + get_target_property(ZLIB_LIBRARY_RELEASE zlibstatic LOCATION) + get_target_property(ZLIB_LIBRARY_DEBUG zlibstatic LOCATION_Debug) endif() find_package(PkgConfig) diff --git a/core/deps/libchdr b/core/deps/libchdr index 925400c4c..2a1119c68 160000 --- a/core/deps/libchdr +++ b/core/deps/libchdr @@ -1 +1 @@ -Subproject commit 925400c4c4b67bafdff8dfad9a1474b22d980777 +Subproject commit 2a1119c686eb07033d02f8c6d12406f8fd373775 From 5c8d7021f661410aa73b5a2a8d038adf4cef3a06 Mon Sep 17 00:00:00 2001 From: scribam Date: Sat, 17 Feb 2024 12:26:43 +0100 Subject: [PATCH 11/86] linux: use egl with x11 instead of glx --- CMakeLists.txt | 14 +- core/deps/glad/include/glad/glx.h | 601 ------------------------------ core/deps/glad/src/glx.c | 387 ------------------- core/linux-dist/x11.cpp | 19 +- core/wsi/egl.cpp | 4 +- core/wsi/gl_context.h | 6 +- core/wsi/xgl.cpp | 172 --------- core/wsi/xgl.h | 41 -- 8 files changed, 11 insertions(+), 1233 deletions(-) delete mode 100644 core/deps/glad/include/glad/glx.h delete mode 100644 core/deps/glad/src/glx.c delete mode 100644 core/wsi/xgl.cpp delete mode 100644 core/wsi/xgl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a32240fd9..24a141659 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1165,9 +1165,7 @@ if(USE_OPENGL) core/wsi/osx.cpp core/wsi/osx.h core/wsi/sdl.cpp - core/wsi/sdl.h - core/wsi/xgl.cpp - core/wsi/xgl.h) + core/wsi/sdl.h) target_sources(${PROJECT_NAME} PRIVATE core/rend/gles/glcache.h @@ -1483,13 +1481,9 @@ endif() if((USE_OPENGL OR USE_GLES2 OR USE_GLES) AND NOT LIBRETRO) add_library(glad STATIC core/deps/glad/src/gl.c) - if(NOT SDL2_FOUND) - if(X11_FOUND) - target_sources(glad PRIVATE core/deps/glad/src/glx.c) - endif() - if(ANDROID OR USE_GLES2 OR USE_GLES) - target_sources(glad PRIVATE core/deps/glad/src/egl.c) - endif() + if(NOT APPLE AND NOT WIN32 AND NOT SDL2_FOUND) + # When SDL2 is not found, we can use EGL with ANativeWindow (Android), DispmanX (Raspberry Pi) or X11 + target_sources(glad PRIVATE core/deps/glad/src/egl.c) endif() target_include_directories(glad PUBLIC core/deps/glad/include) target_link_libraries(${PROJECT_NAME} PRIVATE glad) diff --git a/core/deps/glad/include/glad/glx.h b/core/deps/glad/include/glad/glx.h deleted file mode 100644 index 727aa1cc5..000000000 --- a/core/deps/glad/include/glad/glx.h +++ /dev/null @@ -1,601 +0,0 @@ -/** - * Loader generated by glad 2.0.4 on Sat Jan 20 11:00:00 2024 - * - * SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0 - * - * Generator: C/C++ - * Specification: glx - * Extensions: 5 - * - * APIs: - * - glx=1.4 - * - * Options: - * - ALIAS = False - * - DEBUG = False - * - HEADER_ONLY = False - * - LOADER = True - * - MX = False - * - ON_DEMAND = False - * - * Commandline: - * --api='glx=1.4' --extensions='GLX_ARB_create_context,GLX_ARB_create_context_profile,GLX_ARB_get_proc_address,GLX_EXT_swap_control,GLX_MESA_swap_control' c --loader - * - * Online: - * http://glad.sh/#api=glx%3D1.4&extensions=GLX_ARB_create_context%2CGLX_ARB_create_context_profile%2CGLX_ARB_get_proc_address%2CGLX_EXT_swap_control%2CGLX_MESA_swap_control&generator=c&options=LOADER - * - */ - -#ifndef GLAD_GLX_H_ -#define GLAD_GLX_H_ - -#ifdef GLX_H - #error GLX header already included (API: glx), remove previous include! -#endif -#define GLX_H 1 - - -#include -#include -#include - -#include - -#define GLAD_GLX -#define GLAD_OPTION_GLX_LOADER - -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef GLAD_PLATFORM_H_ -#define GLAD_PLATFORM_H_ - -#ifndef GLAD_PLATFORM_WIN32 - #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32) || defined(__MINGW32__) - #define GLAD_PLATFORM_WIN32 1 - #else - #define GLAD_PLATFORM_WIN32 0 - #endif -#endif - -#ifndef GLAD_PLATFORM_APPLE - #ifdef __APPLE__ - #define GLAD_PLATFORM_APPLE 1 - #else - #define GLAD_PLATFORM_APPLE 0 - #endif -#endif - -#ifndef GLAD_PLATFORM_EMSCRIPTEN - #ifdef __EMSCRIPTEN__ - #define GLAD_PLATFORM_EMSCRIPTEN 1 - #else - #define GLAD_PLATFORM_EMSCRIPTEN 0 - #endif -#endif - -#ifndef GLAD_PLATFORM_UWP - #if defined(_MSC_VER) && !defined(GLAD_INTERNAL_HAVE_WINAPIFAMILY) - #ifdef __has_include - #if __has_include() - #define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1 - #endif - #elif _MSC_VER >= 1700 && !_USING_V110_SDK71_ - #define GLAD_INTERNAL_HAVE_WINAPIFAMILY 1 - #endif - #endif - - #ifdef GLAD_INTERNAL_HAVE_WINAPIFAMILY - #include - #if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) && WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) - #define GLAD_PLATFORM_UWP 1 - #endif - #endif - - #ifndef GLAD_PLATFORM_UWP - #define GLAD_PLATFORM_UWP 0 - #endif -#endif - -#ifdef __GNUC__ - #define GLAD_GNUC_EXTENSION __extension__ -#else - #define GLAD_GNUC_EXTENSION -#endif - -#define GLAD_UNUSED(x) (void)(x) - -#ifndef GLAD_API_CALL - #if defined(GLAD_API_CALL_EXPORT) - #if GLAD_PLATFORM_WIN32 || defined(__CYGWIN__) - #if defined(GLAD_API_CALL_EXPORT_BUILD) - #if defined(__GNUC__) - #define GLAD_API_CALL __attribute__ ((dllexport)) extern - #else - #define GLAD_API_CALL __declspec(dllexport) extern - #endif - #else - #if defined(__GNUC__) - #define GLAD_API_CALL __attribute__ ((dllimport)) extern - #else - #define GLAD_API_CALL __declspec(dllimport) extern - #endif - #endif - #elif defined(__GNUC__) && defined(GLAD_API_CALL_EXPORT_BUILD) - #define GLAD_API_CALL __attribute__ ((visibility ("default"))) extern - #else - #define GLAD_API_CALL extern - #endif - #else - #define GLAD_API_CALL extern - #endif -#endif - -#ifdef APIENTRY - #define GLAD_API_PTR APIENTRY -#elif GLAD_PLATFORM_WIN32 - #define GLAD_API_PTR __stdcall -#else - #define GLAD_API_PTR -#endif - -#ifndef GLAPI -#define GLAPI GLAD_API_CALL -#endif - -#ifndef GLAPIENTRY -#define GLAPIENTRY GLAD_API_PTR -#endif - -#define GLAD_MAKE_VERSION(major, minor) (major * 10000 + minor) -#define GLAD_VERSION_MAJOR(version) (version / 10000) -#define GLAD_VERSION_MINOR(version) (version % 10000) - -#define GLAD_GENERATOR_VERSION "2.0.4" - -typedef void (*GLADapiproc)(void); - -typedef GLADapiproc (*GLADloadfunc)(const char *name); -typedef GLADapiproc (*GLADuserptrloadfunc)(void *userptr, const char *name); - -typedef void (*GLADprecallback)(const char *name, GLADapiproc apiproc, int len_args, ...); -typedef void (*GLADpostcallback)(void *ret, const char *name, GLADapiproc apiproc, int len_args, ...); - -#endif /* GLAD_PLATFORM_H_ */ - -#define GLX_ACCUM_ALPHA_SIZE 17 -#define GLX_ACCUM_BLUE_SIZE 16 -#define GLX_ACCUM_BUFFER_BIT 0x00000080 -#define GLX_ACCUM_GREEN_SIZE 15 -#define GLX_ACCUM_RED_SIZE 14 -#define GLX_ALPHA_SIZE 11 -#define GLX_AUX_BUFFERS 7 -#define GLX_AUX_BUFFERS_BIT 0x00000010 -#define GLX_BACK_LEFT_BUFFER_BIT 0x00000004 -#define GLX_BACK_RIGHT_BUFFER_BIT 0x00000008 -#define GLX_BAD_ATTRIBUTE 2 -#define GLX_BAD_CONTEXT 5 -#define GLX_BAD_ENUM 7 -#define GLX_BAD_SCREEN 1 -#define GLX_BAD_VALUE 6 -#define GLX_BAD_VISUAL 4 -#define GLX_BLUE_SIZE 10 -#define GLX_BUFFER_SIZE 2 -#define GLX_BufferSwapComplete 1 -#define GLX_COLOR_INDEX_BIT 0x00000002 -#define GLX_COLOR_INDEX_TYPE 0x8015 -#define GLX_CONFIG_CAVEAT 0x20 -#define GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB 0x00000002 -#define GLX_CONTEXT_CORE_PROFILE_BIT_ARB 0x00000001 -#define GLX_CONTEXT_DEBUG_BIT_ARB 0x00000001 -#define GLX_CONTEXT_FLAGS_ARB 0x2094 -#define GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB 0x00000002 -#define GLX_CONTEXT_MAJOR_VERSION_ARB 0x2091 -#define GLX_CONTEXT_MINOR_VERSION_ARB 0x2092 -#define GLX_CONTEXT_PROFILE_MASK_ARB 0x9126 -#define GLX_DAMAGED 0x8020 -#define GLX_DEPTH_BUFFER_BIT 0x00000020 -#define GLX_DEPTH_SIZE 12 -#define GLX_DIRECT_COLOR 0x8003 -#define GLX_DONT_CARE 0xFFFFFFFF -#define GLX_DOUBLEBUFFER 5 -#define GLX_DRAWABLE_TYPE 0x8010 -#define GLX_EVENT_MASK 0x801F -#define GLX_EXTENSIONS 0x3 -#define GLX_EXTENSION_NAME "GLX" -#define GLX_FBCONFIG_ID 0x8013 -#define GLX_FRONT_LEFT_BUFFER_BIT 0x00000001 -#define GLX_FRONT_RIGHT_BUFFER_BIT 0x00000002 -#define GLX_GRAY_SCALE 0x8006 -#define GLX_GREEN_SIZE 9 -#define GLX_HEIGHT 0x801E -#define GLX_LARGEST_PBUFFER 0x801C -#define GLX_LEVEL 3 -#define GLX_MAX_PBUFFER_HEIGHT 0x8017 -#define GLX_MAX_PBUFFER_PIXELS 0x8018 -#define GLX_MAX_PBUFFER_WIDTH 0x8016 -#define GLX_MAX_SWAP_INTERVAL_EXT 0x20F2 -#define GLX_NONE 0x8000 -#define GLX_NON_CONFORMANT_CONFIG 0x800D -#define GLX_NO_EXTENSION 3 -#define GLX_PBUFFER 0x8023 -#define GLX_PBUFFER_BIT 0x00000004 -#define GLX_PBUFFER_CLOBBER_MASK 0x08000000 -#define GLX_PBUFFER_HEIGHT 0x8040 -#define GLX_PBUFFER_WIDTH 0x8041 -#define GLX_PIXMAP_BIT 0x00000002 -#define GLX_PRESERVED_CONTENTS 0x801B -#define GLX_PSEUDO_COLOR 0x8004 -#define GLX_PbufferClobber 0 -#define GLX_RED_SIZE 8 -#define GLX_RENDER_TYPE 0x8011 -#define GLX_RGBA 4 -#define GLX_RGBA_BIT 0x00000001 -#define GLX_RGBA_TYPE 0x8014 -#define GLX_SAMPLES 100001 -#define GLX_SAMPLE_BUFFERS 100000 -#define GLX_SAVED 0x8021 -#define GLX_SCREEN 0x800C -#define GLX_SLOW_CONFIG 0x8001 -#define GLX_STATIC_COLOR 0x8005 -#define GLX_STATIC_GRAY 0x8007 -#define GLX_STENCIL_BUFFER_BIT 0x00000040 -#define GLX_STENCIL_SIZE 13 -#define GLX_STEREO 6 -#define GLX_SWAP_INTERVAL_EXT 0x20F1 -#define GLX_TRANSPARENT_ALPHA_VALUE 0x28 -#define GLX_TRANSPARENT_BLUE_VALUE 0x27 -#define GLX_TRANSPARENT_GREEN_VALUE 0x26 -#define GLX_TRANSPARENT_INDEX 0x8009 -#define GLX_TRANSPARENT_INDEX_VALUE 0x24 -#define GLX_TRANSPARENT_RED_VALUE 0x25 -#define GLX_TRANSPARENT_RGB 0x8008 -#define GLX_TRANSPARENT_TYPE 0x23 -#define GLX_TRUE_COLOR 0x8002 -#define GLX_USE_GL 1 -#define GLX_VENDOR 0x1 -#define GLX_VERSION 0x2 -#define GLX_VISUAL_ID 0x800B -#define GLX_WIDTH 0x801D -#define GLX_WINDOW 0x8022 -#define GLX_WINDOW_BIT 0x00000001 -#define GLX_X_RENDERABLE 0x8012 -#define GLX_X_VISUAL_TYPE 0x22 -#define __GLX_NUMBER_EVENTS 17 - - -#ifndef GLEXT_64_TYPES_DEFINED -/* This code block is duplicated in glext.h, so must be protected */ -#define GLEXT_64_TYPES_DEFINED -/* Define int32_t, int64_t, and uint64_t types for UST/MSC */ -/* (as used in the GLX_OML_sync_control extension). */ -#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L -#include -#elif defined(__sun__) || defined(__digital__) -#include -#if defined(__STDC__) -#if defined(__arch64__) || defined(_LP64) -typedef long int int64_t; -typedef unsigned long int uint64_t; -#else -typedef long long int int64_t; -typedef unsigned long long int uint64_t; -#endif /* __arch64__ */ -#endif /* __STDC__ */ -#elif defined( __VMS ) || defined(__sgi) -#include -#elif defined(__SCO__) || defined(__USLC__) -#include -#elif defined(__UNIXOS2__) || defined(__SOL64__) -typedef long int int32_t; -typedef long long int int64_t; -typedef unsigned long long int uint64_t; -#elif defined(_WIN32) && defined(__GNUC__) -#include -#elif defined(_WIN32) -typedef __int32 int32_t; -typedef __int64 int64_t; -typedef unsigned __int64 uint64_t; -#else -/* Fallback if nothing above works */ -#include -#endif -#endif - - - - - - - - - - - - - - - - - -#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060) - -#else - -#endif - -#if defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && (__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ > 1060) - -#else - -#endif - - - - - - - -typedef XID GLXFBConfigID; -typedef struct __GLXFBConfigRec *GLXFBConfig; -typedef XID GLXContextID; -typedef struct __GLXcontextRec *GLXContext; -typedef XID GLXPixmap; -typedef XID GLXDrawable; -typedef XID GLXWindow; -typedef XID GLXPbuffer; -typedef void (GLAD_API_PTR *__GLXextFuncPtr)(void); -typedef XID GLXVideoCaptureDeviceNV; -typedef unsigned int GLXVideoDeviceNV; -typedef XID GLXVideoSourceSGIX; -typedef XID GLXFBConfigIDSGIX; -typedef struct __GLXFBConfigRec *GLXFBConfigSGIX; -typedef XID GLXPbufferSGIX; -typedef struct { - int event_type; /* GLX_DAMAGED or GLX_SAVED */ - int draw_type; /* GLX_WINDOW or GLX_PBUFFER */ - unsigned long serial; /* # of last request processed by server */ - Bool send_event; /* true if this came for SendEvent request */ - Display *display; /* display the event was read from */ - GLXDrawable drawable; /* XID of Drawable */ - unsigned int buffer_mask; /* mask indicating which buffers are affected */ - unsigned int aux_buffer; /* which aux buffer was affected */ - int x, y; - int width, height; - int count; /* if nonzero, at least this many more */ -} GLXPbufferClobberEvent; -typedef struct { - int type; - unsigned long serial; /* # of last request processed by server */ - Bool send_event; /* true if this came from a SendEvent request */ - Display *display; /* Display the event was read from */ - GLXDrawable drawable; /* drawable on which event was requested in event mask */ - int event_type; - int64_t ust; - int64_t msc; - int64_t sbc; -} GLXBufferSwapComplete; -typedef union __GLXEvent { - GLXPbufferClobberEvent glxpbufferclobber; - GLXBufferSwapComplete glxbufferswapcomplete; - long pad[24]; -} GLXEvent; -typedef struct { - int type; - unsigned long serial; - Bool send_event; - Display *display; - int extension; - int evtype; - GLXDrawable window; - Bool stereo_tree; -} GLXStereoNotifyEventEXT; -typedef struct { - int type; - unsigned long serial; /* # of last request processed by server */ - Bool send_event; /* true if this came for SendEvent request */ - Display *display; /* display the event was read from */ - GLXDrawable drawable; /* i.d. of Drawable */ - int event_type; /* GLX_DAMAGED_SGIX or GLX_SAVED_SGIX */ - int draw_type; /* GLX_WINDOW_SGIX or GLX_PBUFFER_SGIX */ - unsigned int mask; /* mask indicating which buffers are affected*/ - int x, y; - int width, height; - int count; /* if nonzero, at least this many more */ -} GLXBufferClobberEventSGIX; -typedef struct { - char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ - int networkId; -} GLXHyperpipeNetworkSGIX; -typedef struct { - char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ - int channel; - unsigned int participationType; - int timeSlice; -} GLXHyperpipeConfigSGIX; -typedef struct { - char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ - int srcXOrigin, srcYOrigin, srcWidth, srcHeight; - int destXOrigin, destYOrigin, destWidth, destHeight; -} GLXPipeRect; -typedef struct { - char pipeName[80]; /* Should be [GLX_HYPERPIPE_PIPE_NAME_LENGTH_SGIX] */ - int XOrigin, YOrigin, maxHeight, maxWidth; -} GLXPipeRectLimits; - - -#define GLX_VERSION_1_0 1 -GLAD_API_CALL int GLAD_GLX_VERSION_1_0; -#define GLX_VERSION_1_1 1 -GLAD_API_CALL int GLAD_GLX_VERSION_1_1; -#define GLX_VERSION_1_2 1 -GLAD_API_CALL int GLAD_GLX_VERSION_1_2; -#define GLX_VERSION_1_3 1 -GLAD_API_CALL int GLAD_GLX_VERSION_1_3; -#define GLX_VERSION_1_4 1 -GLAD_API_CALL int GLAD_GLX_VERSION_1_4; -#define GLX_ARB_create_context 1 -GLAD_API_CALL int GLAD_GLX_ARB_create_context; -#define GLX_ARB_create_context_profile 1 -GLAD_API_CALL int GLAD_GLX_ARB_create_context_profile; -#define GLX_ARB_get_proc_address 1 -GLAD_API_CALL int GLAD_GLX_ARB_get_proc_address; -#define GLX_EXT_swap_control 1 -GLAD_API_CALL int GLAD_GLX_EXT_swap_control; -#define GLX_MESA_swap_control 1 -GLAD_API_CALL int GLAD_GLX_MESA_swap_control; - - -typedef GLXFBConfig * (GLAD_API_PTR *PFNGLXCHOOSEFBCONFIGPROC)(Display * dpy, int screen, const int * attrib_list, int * nelements); -typedef XVisualInfo * (GLAD_API_PTR *PFNGLXCHOOSEVISUALPROC)(Display * dpy, int screen, int * attribList); -typedef void (GLAD_API_PTR *PFNGLXCOPYCONTEXTPROC)(Display * dpy, GLXContext src, GLXContext dst, unsigned long mask); -typedef GLXContext (GLAD_API_PTR *PFNGLXCREATECONTEXTPROC)(Display * dpy, XVisualInfo * vis, GLXContext shareList, Bool direct); -typedef GLXContext (GLAD_API_PTR *PFNGLXCREATECONTEXTATTRIBSARBPROC)(Display * dpy, GLXFBConfig config, GLXContext share_context, Bool direct, const int * attrib_list); -typedef GLXPixmap (GLAD_API_PTR *PFNGLXCREATEGLXPIXMAPPROC)(Display * dpy, XVisualInfo * visual, Pixmap pixmap); -typedef GLXContext (GLAD_API_PTR *PFNGLXCREATENEWCONTEXTPROC)(Display * dpy, GLXFBConfig config, int render_type, GLXContext share_list, Bool direct); -typedef GLXPbuffer (GLAD_API_PTR *PFNGLXCREATEPBUFFERPROC)(Display * dpy, GLXFBConfig config, const int * attrib_list); -typedef GLXPixmap (GLAD_API_PTR *PFNGLXCREATEPIXMAPPROC)(Display * dpy, GLXFBConfig config, Pixmap pixmap, const int * attrib_list); -typedef GLXWindow (GLAD_API_PTR *PFNGLXCREATEWINDOWPROC)(Display * dpy, GLXFBConfig config, Window win, const int * attrib_list); -typedef void (GLAD_API_PTR *PFNGLXDESTROYCONTEXTPROC)(Display * dpy, GLXContext ctx); -typedef void (GLAD_API_PTR *PFNGLXDESTROYGLXPIXMAPPROC)(Display * dpy, GLXPixmap pixmap); -typedef void (GLAD_API_PTR *PFNGLXDESTROYPBUFFERPROC)(Display * dpy, GLXPbuffer pbuf); -typedef void (GLAD_API_PTR *PFNGLXDESTROYPIXMAPPROC)(Display * dpy, GLXPixmap pixmap); -typedef void (GLAD_API_PTR *PFNGLXDESTROYWINDOWPROC)(Display * dpy, GLXWindow win); -typedef const char * (GLAD_API_PTR *PFNGLXGETCLIENTSTRINGPROC)(Display * dpy, int name); -typedef int (GLAD_API_PTR *PFNGLXGETCONFIGPROC)(Display * dpy, XVisualInfo * visual, int attrib, int * value); -typedef GLXContext (GLAD_API_PTR *PFNGLXGETCURRENTCONTEXTPROC)(void); -typedef Display * (GLAD_API_PTR *PFNGLXGETCURRENTDISPLAYPROC)(void); -typedef GLXDrawable (GLAD_API_PTR *PFNGLXGETCURRENTDRAWABLEPROC)(void); -typedef GLXDrawable (GLAD_API_PTR *PFNGLXGETCURRENTREADDRAWABLEPROC)(void); -typedef int (GLAD_API_PTR *PFNGLXGETFBCONFIGATTRIBPROC)(Display * dpy, GLXFBConfig config, int attribute, int * value); -typedef GLXFBConfig * (GLAD_API_PTR *PFNGLXGETFBCONFIGSPROC)(Display * dpy, int screen, int * nelements); -typedef __GLXextFuncPtr (GLAD_API_PTR *PFNGLXGETPROCADDRESSPROC)(const GLubyte * procName); -typedef __GLXextFuncPtr (GLAD_API_PTR *PFNGLXGETPROCADDRESSARBPROC)(const GLubyte * procName); -typedef void (GLAD_API_PTR *PFNGLXGETSELECTEDEVENTPROC)(Display * dpy, GLXDrawable draw, unsigned long * event_mask); -typedef int (GLAD_API_PTR *PFNGLXGETSWAPINTERVALMESAPROC)(void); -typedef XVisualInfo * (GLAD_API_PTR *PFNGLXGETVISUALFROMFBCONFIGPROC)(Display * dpy, GLXFBConfig config); -typedef Bool (GLAD_API_PTR *PFNGLXISDIRECTPROC)(Display * dpy, GLXContext ctx); -typedef Bool (GLAD_API_PTR *PFNGLXMAKECONTEXTCURRENTPROC)(Display * dpy, GLXDrawable draw, GLXDrawable read, GLXContext ctx); -typedef Bool (GLAD_API_PTR *PFNGLXMAKECURRENTPROC)(Display * dpy, GLXDrawable drawable, GLXContext ctx); -typedef int (GLAD_API_PTR *PFNGLXQUERYCONTEXTPROC)(Display * dpy, GLXContext ctx, int attribute, int * value); -typedef void (GLAD_API_PTR *PFNGLXQUERYDRAWABLEPROC)(Display * dpy, GLXDrawable draw, int attribute, unsigned int * value); -typedef Bool (GLAD_API_PTR *PFNGLXQUERYEXTENSIONPROC)(Display * dpy, int * errorb, int * event); -typedef const char * (GLAD_API_PTR *PFNGLXQUERYEXTENSIONSSTRINGPROC)(Display * dpy, int screen); -typedef const char * (GLAD_API_PTR *PFNGLXQUERYSERVERSTRINGPROC)(Display * dpy, int screen, int name); -typedef Bool (GLAD_API_PTR *PFNGLXQUERYVERSIONPROC)(Display * dpy, int * maj, int * min); -typedef void (GLAD_API_PTR *PFNGLXSELECTEVENTPROC)(Display * dpy, GLXDrawable draw, unsigned long event_mask); -typedef void (GLAD_API_PTR *PFNGLXSWAPBUFFERSPROC)(Display * dpy, GLXDrawable drawable); -typedef void (GLAD_API_PTR *PFNGLXSWAPINTERVALEXTPROC)(Display * dpy, GLXDrawable drawable, int interval); -typedef int (GLAD_API_PTR *PFNGLXSWAPINTERVALMESAPROC)(unsigned int interval); -typedef void (GLAD_API_PTR *PFNGLXUSEXFONTPROC)(Font font, int first, int count, int list); -typedef void (GLAD_API_PTR *PFNGLXWAITGLPROC)(void); -typedef void (GLAD_API_PTR *PFNGLXWAITXPROC)(void); - -GLAD_API_CALL PFNGLXCHOOSEFBCONFIGPROC glad_glXChooseFBConfig; -#define glXChooseFBConfig glad_glXChooseFBConfig -GLAD_API_CALL PFNGLXCHOOSEVISUALPROC glad_glXChooseVisual; -#define glXChooseVisual glad_glXChooseVisual -GLAD_API_CALL PFNGLXCOPYCONTEXTPROC glad_glXCopyContext; -#define glXCopyContext glad_glXCopyContext -GLAD_API_CALL PFNGLXCREATECONTEXTPROC glad_glXCreateContext; -#define glXCreateContext glad_glXCreateContext -GLAD_API_CALL PFNGLXCREATECONTEXTATTRIBSARBPROC glad_glXCreateContextAttribsARB; -#define glXCreateContextAttribsARB glad_glXCreateContextAttribsARB -GLAD_API_CALL PFNGLXCREATEGLXPIXMAPPROC glad_glXCreateGLXPixmap; -#define glXCreateGLXPixmap glad_glXCreateGLXPixmap -GLAD_API_CALL PFNGLXCREATENEWCONTEXTPROC glad_glXCreateNewContext; -#define glXCreateNewContext glad_glXCreateNewContext -GLAD_API_CALL PFNGLXCREATEPBUFFERPROC glad_glXCreatePbuffer; -#define glXCreatePbuffer glad_glXCreatePbuffer -GLAD_API_CALL PFNGLXCREATEPIXMAPPROC glad_glXCreatePixmap; -#define glXCreatePixmap glad_glXCreatePixmap -GLAD_API_CALL PFNGLXCREATEWINDOWPROC glad_glXCreateWindow; -#define glXCreateWindow glad_glXCreateWindow -GLAD_API_CALL PFNGLXDESTROYCONTEXTPROC glad_glXDestroyContext; -#define glXDestroyContext glad_glXDestroyContext -GLAD_API_CALL PFNGLXDESTROYGLXPIXMAPPROC glad_glXDestroyGLXPixmap; -#define glXDestroyGLXPixmap glad_glXDestroyGLXPixmap -GLAD_API_CALL PFNGLXDESTROYPBUFFERPROC glad_glXDestroyPbuffer; -#define glXDestroyPbuffer glad_glXDestroyPbuffer -GLAD_API_CALL PFNGLXDESTROYPIXMAPPROC glad_glXDestroyPixmap; -#define glXDestroyPixmap glad_glXDestroyPixmap -GLAD_API_CALL PFNGLXDESTROYWINDOWPROC glad_glXDestroyWindow; -#define glXDestroyWindow glad_glXDestroyWindow -GLAD_API_CALL PFNGLXGETCLIENTSTRINGPROC glad_glXGetClientString; -#define glXGetClientString glad_glXGetClientString -GLAD_API_CALL PFNGLXGETCONFIGPROC glad_glXGetConfig; -#define glXGetConfig glad_glXGetConfig -GLAD_API_CALL PFNGLXGETCURRENTCONTEXTPROC glad_glXGetCurrentContext; -#define glXGetCurrentContext glad_glXGetCurrentContext -GLAD_API_CALL PFNGLXGETCURRENTDISPLAYPROC glad_glXGetCurrentDisplay; -#define glXGetCurrentDisplay glad_glXGetCurrentDisplay -GLAD_API_CALL PFNGLXGETCURRENTDRAWABLEPROC glad_glXGetCurrentDrawable; -#define glXGetCurrentDrawable glad_glXGetCurrentDrawable -GLAD_API_CALL PFNGLXGETCURRENTREADDRAWABLEPROC glad_glXGetCurrentReadDrawable; -#define glXGetCurrentReadDrawable glad_glXGetCurrentReadDrawable -GLAD_API_CALL PFNGLXGETFBCONFIGATTRIBPROC glad_glXGetFBConfigAttrib; -#define glXGetFBConfigAttrib glad_glXGetFBConfigAttrib -GLAD_API_CALL PFNGLXGETFBCONFIGSPROC glad_glXGetFBConfigs; -#define glXGetFBConfigs glad_glXGetFBConfigs -GLAD_API_CALL PFNGLXGETPROCADDRESSPROC glad_glXGetProcAddress; -#define glXGetProcAddress glad_glXGetProcAddress -GLAD_API_CALL PFNGLXGETPROCADDRESSARBPROC glad_glXGetProcAddressARB; -#define glXGetProcAddressARB glad_glXGetProcAddressARB -GLAD_API_CALL PFNGLXGETSELECTEDEVENTPROC glad_glXGetSelectedEvent; -#define glXGetSelectedEvent glad_glXGetSelectedEvent -GLAD_API_CALL PFNGLXGETSWAPINTERVALMESAPROC glad_glXGetSwapIntervalMESA; -#define glXGetSwapIntervalMESA glad_glXGetSwapIntervalMESA -GLAD_API_CALL PFNGLXGETVISUALFROMFBCONFIGPROC glad_glXGetVisualFromFBConfig; -#define glXGetVisualFromFBConfig glad_glXGetVisualFromFBConfig -GLAD_API_CALL PFNGLXISDIRECTPROC glad_glXIsDirect; -#define glXIsDirect glad_glXIsDirect -GLAD_API_CALL PFNGLXMAKECONTEXTCURRENTPROC glad_glXMakeContextCurrent; -#define glXMakeContextCurrent glad_glXMakeContextCurrent -GLAD_API_CALL PFNGLXMAKECURRENTPROC glad_glXMakeCurrent; -#define glXMakeCurrent glad_glXMakeCurrent -GLAD_API_CALL PFNGLXQUERYCONTEXTPROC glad_glXQueryContext; -#define glXQueryContext glad_glXQueryContext -GLAD_API_CALL PFNGLXQUERYDRAWABLEPROC glad_glXQueryDrawable; -#define glXQueryDrawable glad_glXQueryDrawable -GLAD_API_CALL PFNGLXQUERYEXTENSIONPROC glad_glXQueryExtension; -#define glXQueryExtension glad_glXQueryExtension -GLAD_API_CALL PFNGLXQUERYEXTENSIONSSTRINGPROC glad_glXQueryExtensionsString; -#define glXQueryExtensionsString glad_glXQueryExtensionsString -GLAD_API_CALL PFNGLXQUERYSERVERSTRINGPROC glad_glXQueryServerString; -#define glXQueryServerString glad_glXQueryServerString -GLAD_API_CALL PFNGLXQUERYVERSIONPROC glad_glXQueryVersion; -#define glXQueryVersion glad_glXQueryVersion -GLAD_API_CALL PFNGLXSELECTEVENTPROC glad_glXSelectEvent; -#define glXSelectEvent glad_glXSelectEvent -GLAD_API_CALL PFNGLXSWAPBUFFERSPROC glad_glXSwapBuffers; -#define glXSwapBuffers glad_glXSwapBuffers -GLAD_API_CALL PFNGLXSWAPINTERVALEXTPROC glad_glXSwapIntervalEXT; -#define glXSwapIntervalEXT glad_glXSwapIntervalEXT -GLAD_API_CALL PFNGLXSWAPINTERVALMESAPROC glad_glXSwapIntervalMESA; -#define glXSwapIntervalMESA glad_glXSwapIntervalMESA -GLAD_API_CALL PFNGLXUSEXFONTPROC glad_glXUseXFont; -#define glXUseXFont glad_glXUseXFont -GLAD_API_CALL PFNGLXWAITGLPROC glad_glXWaitGL; -#define glXWaitGL glad_glXWaitGL -GLAD_API_CALL PFNGLXWAITXPROC glad_glXWaitX; -#define glXWaitX glad_glXWaitX - - - - - -GLAD_API_CALL int gladLoadGLXUserPtr(Display *display, int screen, GLADuserptrloadfunc load, void *userptr); -GLAD_API_CALL int gladLoadGLX(Display *display, int screen, GLADloadfunc load); - -#ifdef GLAD_GLX - -GLAD_API_CALL int gladLoaderLoadGLX(Display *display, int screen); - -GLAD_API_CALL void gladLoaderUnloadGLX(void); - -#endif -#ifdef __cplusplus -} -#endif -#endif diff --git a/core/deps/glad/src/glx.c b/core/deps/glad/src/glx.c deleted file mode 100644 index 4c9567707..000000000 --- a/core/deps/glad/src/glx.c +++ /dev/null @@ -1,387 +0,0 @@ -/** - * SPDX-License-Identifier: (WTFPL OR CC0-1.0) AND Apache-2.0 - */ -#include -#include -#include -#include - -#ifndef GLAD_IMPL_UTIL_C_ -#define GLAD_IMPL_UTIL_C_ - -#ifdef _MSC_VER -#define GLAD_IMPL_UTIL_SSCANF sscanf_s -#else -#define GLAD_IMPL_UTIL_SSCANF sscanf -#endif - -#endif /* GLAD_IMPL_UTIL_C_ */ - -#ifdef __cplusplus -extern "C" { -#endif - - - -int GLAD_GLX_VERSION_1_0 = 0; -int GLAD_GLX_VERSION_1_1 = 0; -int GLAD_GLX_VERSION_1_2 = 0; -int GLAD_GLX_VERSION_1_3 = 0; -int GLAD_GLX_VERSION_1_4 = 0; -int GLAD_GLX_ARB_create_context = 0; -int GLAD_GLX_ARB_create_context_profile = 0; -int GLAD_GLX_ARB_get_proc_address = 0; -int GLAD_GLX_EXT_swap_control = 0; -int GLAD_GLX_MESA_swap_control = 0; - - - -PFNGLXCHOOSEFBCONFIGPROC glad_glXChooseFBConfig = NULL; -PFNGLXCHOOSEVISUALPROC glad_glXChooseVisual = NULL; -PFNGLXCOPYCONTEXTPROC glad_glXCopyContext = NULL; -PFNGLXCREATECONTEXTPROC glad_glXCreateContext = NULL; -PFNGLXCREATECONTEXTATTRIBSARBPROC glad_glXCreateContextAttribsARB = NULL; -PFNGLXCREATEGLXPIXMAPPROC glad_glXCreateGLXPixmap = NULL; -PFNGLXCREATENEWCONTEXTPROC glad_glXCreateNewContext = NULL; -PFNGLXCREATEPBUFFERPROC glad_glXCreatePbuffer = NULL; -PFNGLXCREATEPIXMAPPROC glad_glXCreatePixmap = NULL; -PFNGLXCREATEWINDOWPROC glad_glXCreateWindow = NULL; -PFNGLXDESTROYCONTEXTPROC glad_glXDestroyContext = NULL; -PFNGLXDESTROYGLXPIXMAPPROC glad_glXDestroyGLXPixmap = NULL; -PFNGLXDESTROYPBUFFERPROC glad_glXDestroyPbuffer = NULL; -PFNGLXDESTROYPIXMAPPROC glad_glXDestroyPixmap = NULL; -PFNGLXDESTROYWINDOWPROC glad_glXDestroyWindow = NULL; -PFNGLXGETCLIENTSTRINGPROC glad_glXGetClientString = NULL; -PFNGLXGETCONFIGPROC glad_glXGetConfig = NULL; -PFNGLXGETCURRENTCONTEXTPROC glad_glXGetCurrentContext = NULL; -PFNGLXGETCURRENTDISPLAYPROC glad_glXGetCurrentDisplay = NULL; -PFNGLXGETCURRENTDRAWABLEPROC glad_glXGetCurrentDrawable = NULL; -PFNGLXGETCURRENTREADDRAWABLEPROC glad_glXGetCurrentReadDrawable = NULL; -PFNGLXGETFBCONFIGATTRIBPROC glad_glXGetFBConfigAttrib = NULL; -PFNGLXGETFBCONFIGSPROC glad_glXGetFBConfigs = NULL; -PFNGLXGETPROCADDRESSPROC glad_glXGetProcAddress = NULL; -PFNGLXGETPROCADDRESSARBPROC glad_glXGetProcAddressARB = NULL; -PFNGLXGETSELECTEDEVENTPROC glad_glXGetSelectedEvent = NULL; -PFNGLXGETSWAPINTERVALMESAPROC glad_glXGetSwapIntervalMESA = NULL; -PFNGLXGETVISUALFROMFBCONFIGPROC glad_glXGetVisualFromFBConfig = NULL; -PFNGLXISDIRECTPROC glad_glXIsDirect = NULL; -PFNGLXMAKECONTEXTCURRENTPROC glad_glXMakeContextCurrent = NULL; -PFNGLXMAKECURRENTPROC glad_glXMakeCurrent = NULL; -PFNGLXQUERYCONTEXTPROC glad_glXQueryContext = NULL; -PFNGLXQUERYDRAWABLEPROC glad_glXQueryDrawable = NULL; -PFNGLXQUERYEXTENSIONPROC glad_glXQueryExtension = NULL; -PFNGLXQUERYEXTENSIONSSTRINGPROC glad_glXQueryExtensionsString = NULL; -PFNGLXQUERYSERVERSTRINGPROC glad_glXQueryServerString = NULL; -PFNGLXQUERYVERSIONPROC glad_glXQueryVersion = NULL; -PFNGLXSELECTEVENTPROC glad_glXSelectEvent = NULL; -PFNGLXSWAPBUFFERSPROC glad_glXSwapBuffers = NULL; -PFNGLXSWAPINTERVALEXTPROC glad_glXSwapIntervalEXT = NULL; -PFNGLXSWAPINTERVALMESAPROC glad_glXSwapIntervalMESA = NULL; -PFNGLXUSEXFONTPROC glad_glXUseXFont = NULL; -PFNGLXWAITGLPROC glad_glXWaitGL = NULL; -PFNGLXWAITXPROC glad_glXWaitX = NULL; - - -static void glad_glx_load_GLX_VERSION_1_0( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_VERSION_1_0) return; - glad_glXChooseVisual = (PFNGLXCHOOSEVISUALPROC) load(userptr, "glXChooseVisual"); - glad_glXCopyContext = (PFNGLXCOPYCONTEXTPROC) load(userptr, "glXCopyContext"); - glad_glXCreateContext = (PFNGLXCREATECONTEXTPROC) load(userptr, "glXCreateContext"); - glad_glXCreateGLXPixmap = (PFNGLXCREATEGLXPIXMAPPROC) load(userptr, "glXCreateGLXPixmap"); - glad_glXDestroyContext = (PFNGLXDESTROYCONTEXTPROC) load(userptr, "glXDestroyContext"); - glad_glXDestroyGLXPixmap = (PFNGLXDESTROYGLXPIXMAPPROC) load(userptr, "glXDestroyGLXPixmap"); - glad_glXGetConfig = (PFNGLXGETCONFIGPROC) load(userptr, "glXGetConfig"); - glad_glXGetCurrentContext = (PFNGLXGETCURRENTCONTEXTPROC) load(userptr, "glXGetCurrentContext"); - glad_glXGetCurrentDrawable = (PFNGLXGETCURRENTDRAWABLEPROC) load(userptr, "glXGetCurrentDrawable"); - glad_glXIsDirect = (PFNGLXISDIRECTPROC) load(userptr, "glXIsDirect"); - glad_glXMakeCurrent = (PFNGLXMAKECURRENTPROC) load(userptr, "glXMakeCurrent"); - glad_glXQueryExtension = (PFNGLXQUERYEXTENSIONPROC) load(userptr, "glXQueryExtension"); - glad_glXQueryVersion = (PFNGLXQUERYVERSIONPROC) load(userptr, "glXQueryVersion"); - glad_glXSwapBuffers = (PFNGLXSWAPBUFFERSPROC) load(userptr, "glXSwapBuffers"); - glad_glXUseXFont = (PFNGLXUSEXFONTPROC) load(userptr, "glXUseXFont"); - glad_glXWaitGL = (PFNGLXWAITGLPROC) load(userptr, "glXWaitGL"); - glad_glXWaitX = (PFNGLXWAITXPROC) load(userptr, "glXWaitX"); -} -static void glad_glx_load_GLX_VERSION_1_1( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_VERSION_1_1) return; - glad_glXGetClientString = (PFNGLXGETCLIENTSTRINGPROC) load(userptr, "glXGetClientString"); - glad_glXQueryExtensionsString = (PFNGLXQUERYEXTENSIONSSTRINGPROC) load(userptr, "glXQueryExtensionsString"); - glad_glXQueryServerString = (PFNGLXQUERYSERVERSTRINGPROC) load(userptr, "glXQueryServerString"); -} -static void glad_glx_load_GLX_VERSION_1_2( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_VERSION_1_2) return; - glad_glXGetCurrentDisplay = (PFNGLXGETCURRENTDISPLAYPROC) load(userptr, "glXGetCurrentDisplay"); -} -static void glad_glx_load_GLX_VERSION_1_3( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_VERSION_1_3) return; - glad_glXChooseFBConfig = (PFNGLXCHOOSEFBCONFIGPROC) load(userptr, "glXChooseFBConfig"); - glad_glXCreateNewContext = (PFNGLXCREATENEWCONTEXTPROC) load(userptr, "glXCreateNewContext"); - glad_glXCreatePbuffer = (PFNGLXCREATEPBUFFERPROC) load(userptr, "glXCreatePbuffer"); - glad_glXCreatePixmap = (PFNGLXCREATEPIXMAPPROC) load(userptr, "glXCreatePixmap"); - glad_glXCreateWindow = (PFNGLXCREATEWINDOWPROC) load(userptr, "glXCreateWindow"); - glad_glXDestroyPbuffer = (PFNGLXDESTROYPBUFFERPROC) load(userptr, "glXDestroyPbuffer"); - glad_glXDestroyPixmap = (PFNGLXDESTROYPIXMAPPROC) load(userptr, "glXDestroyPixmap"); - glad_glXDestroyWindow = (PFNGLXDESTROYWINDOWPROC) load(userptr, "glXDestroyWindow"); - glad_glXGetCurrentReadDrawable = (PFNGLXGETCURRENTREADDRAWABLEPROC) load(userptr, "glXGetCurrentReadDrawable"); - glad_glXGetFBConfigAttrib = (PFNGLXGETFBCONFIGATTRIBPROC) load(userptr, "glXGetFBConfigAttrib"); - glad_glXGetFBConfigs = (PFNGLXGETFBCONFIGSPROC) load(userptr, "glXGetFBConfigs"); - glad_glXGetSelectedEvent = (PFNGLXGETSELECTEDEVENTPROC) load(userptr, "glXGetSelectedEvent"); - glad_glXGetVisualFromFBConfig = (PFNGLXGETVISUALFROMFBCONFIGPROC) load(userptr, "glXGetVisualFromFBConfig"); - glad_glXMakeContextCurrent = (PFNGLXMAKECONTEXTCURRENTPROC) load(userptr, "glXMakeContextCurrent"); - glad_glXQueryContext = (PFNGLXQUERYCONTEXTPROC) load(userptr, "glXQueryContext"); - glad_glXQueryDrawable = (PFNGLXQUERYDRAWABLEPROC) load(userptr, "glXQueryDrawable"); - glad_glXSelectEvent = (PFNGLXSELECTEVENTPROC) load(userptr, "glXSelectEvent"); -} -static void glad_glx_load_GLX_VERSION_1_4( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_VERSION_1_4) return; - glad_glXGetProcAddress = (PFNGLXGETPROCADDRESSPROC) load(userptr, "glXGetProcAddress"); -} -static void glad_glx_load_GLX_ARB_create_context( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_ARB_create_context) return; - glad_glXCreateContextAttribsARB = (PFNGLXCREATECONTEXTATTRIBSARBPROC) load(userptr, "glXCreateContextAttribsARB"); -} -static void glad_glx_load_GLX_ARB_get_proc_address( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_ARB_get_proc_address) return; - glad_glXGetProcAddressARB = (PFNGLXGETPROCADDRESSARBPROC) load(userptr, "glXGetProcAddressARB"); -} -static void glad_glx_load_GLX_EXT_swap_control( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_EXT_swap_control) return; - glad_glXSwapIntervalEXT = (PFNGLXSWAPINTERVALEXTPROC) load(userptr, "glXSwapIntervalEXT"); -} -static void glad_glx_load_GLX_MESA_swap_control( GLADuserptrloadfunc load, void* userptr) { - if(!GLAD_GLX_MESA_swap_control) return; - glad_glXGetSwapIntervalMESA = (PFNGLXGETSWAPINTERVALMESAPROC) load(userptr, "glXGetSwapIntervalMESA"); - glad_glXSwapIntervalMESA = (PFNGLXSWAPINTERVALMESAPROC) load(userptr, "glXSwapIntervalMESA"); -} - - - -static int glad_glx_has_extension(Display *display, int screen, const char *ext) { -#ifndef GLX_VERSION_1_1 - GLAD_UNUSED(display); - GLAD_UNUSED(screen); - GLAD_UNUSED(ext); -#else - const char *terminator; - const char *loc; - const char *extensions; - - if (glXQueryExtensionsString == NULL) { - return 0; - } - - extensions = glXQueryExtensionsString(display, screen); - - if(extensions == NULL || ext == NULL) { - return 0; - } - - while(1) { - loc = strstr(extensions, ext); - if(loc == NULL) - break; - - terminator = loc + strlen(ext); - if((loc == extensions || *(loc - 1) == ' ') && - (*terminator == ' ' || *terminator == '\0')) { - return 1; - } - extensions = terminator; - } -#endif - - return 0; -} - -static GLADapiproc glad_glx_get_proc_from_userptr(void *userptr, const char* name) { - return (GLAD_GNUC_EXTENSION (GLADapiproc (*)(const char *name)) userptr)(name); -} - -static int glad_glx_find_extensions(Display *display, int screen) { - GLAD_GLX_ARB_create_context = glad_glx_has_extension(display, screen, "GLX_ARB_create_context"); - GLAD_GLX_ARB_create_context_profile = glad_glx_has_extension(display, screen, "GLX_ARB_create_context_profile"); - GLAD_GLX_ARB_get_proc_address = glad_glx_has_extension(display, screen, "GLX_ARB_get_proc_address"); - GLAD_GLX_EXT_swap_control = glad_glx_has_extension(display, screen, "GLX_EXT_swap_control"); - GLAD_GLX_MESA_swap_control = glad_glx_has_extension(display, screen, "GLX_MESA_swap_control"); - return 1; -} - -static int glad_glx_find_core_glx(Display **display, int *screen) { - int major = 0, minor = 0; - if(*display == NULL) { -#ifdef GLAD_GLX_NO_X11 - GLAD_UNUSED(screen); - return 0; -#else - *display = XOpenDisplay(0); - if (*display == NULL) { - return 0; - } - *screen = XScreenNumberOfScreen(XDefaultScreenOfDisplay(*display)); -#endif - } - glXQueryVersion(*display, &major, &minor); - GLAD_GLX_VERSION_1_0 = (major == 1 && minor >= 0) || major > 1; - GLAD_GLX_VERSION_1_1 = (major == 1 && minor >= 1) || major > 1; - GLAD_GLX_VERSION_1_2 = (major == 1 && minor >= 2) || major > 1; - GLAD_GLX_VERSION_1_3 = (major == 1 && minor >= 3) || major > 1; - GLAD_GLX_VERSION_1_4 = (major == 1 && minor >= 4) || major > 1; - return GLAD_MAKE_VERSION(major, minor); -} - -int gladLoadGLXUserPtr(Display *display, int screen, GLADuserptrloadfunc load, void *userptr) { - int version; - glXQueryVersion = (PFNGLXQUERYVERSIONPROC) load(userptr, "glXQueryVersion"); - if(glXQueryVersion == NULL) return 0; - version = glad_glx_find_core_glx(&display, &screen); - - glad_glx_load_GLX_VERSION_1_0(load, userptr); - glad_glx_load_GLX_VERSION_1_1(load, userptr); - glad_glx_load_GLX_VERSION_1_2(load, userptr); - glad_glx_load_GLX_VERSION_1_3(load, userptr); - glad_glx_load_GLX_VERSION_1_4(load, userptr); - - if (!glad_glx_find_extensions(display, screen)) return 0; - glad_glx_load_GLX_ARB_create_context(load, userptr); - glad_glx_load_GLX_ARB_get_proc_address(load, userptr); - glad_glx_load_GLX_EXT_swap_control(load, userptr); - glad_glx_load_GLX_MESA_swap_control(load, userptr); - - - return version; -} - -int gladLoadGLX(Display *display, int screen, GLADloadfunc load) { - return gladLoadGLXUserPtr(display, screen, glad_glx_get_proc_from_userptr, GLAD_GNUC_EXTENSION (void*) load); -} - - - -#ifdef GLAD_GLX - -#ifndef GLAD_LOADER_LIBRARY_C_ -#define GLAD_LOADER_LIBRARY_C_ - -#include -#include - -#if GLAD_PLATFORM_WIN32 -#include -#else -#include -#endif - - -static void* glad_get_dlopen_handle(const char *lib_names[], int length) { - void *handle = NULL; - int i; - - for (i = 0; i < length; ++i) { -#if GLAD_PLATFORM_WIN32 - #if GLAD_PLATFORM_UWP - size_t buffer_size = (strlen(lib_names[i]) + 1) * sizeof(WCHAR); - LPWSTR buffer = (LPWSTR) malloc(buffer_size); - if (buffer != NULL) { - int ret = MultiByteToWideChar(CP_ACP, 0, lib_names[i], -1, buffer, buffer_size); - if (ret != 0) { - handle = (void*) LoadPackagedLibrary(buffer, 0); - } - free((void*) buffer); - } - #else - handle = (void*) LoadLibraryA(lib_names[i]); - #endif -#else - handle = dlopen(lib_names[i], RTLD_LAZY | RTLD_LOCAL); -#endif - if (handle != NULL) { - return handle; - } - } - - return NULL; -} - -static void glad_close_dlopen_handle(void* handle) { - if (handle != NULL) { -#if GLAD_PLATFORM_WIN32 - FreeLibrary((HMODULE) handle); -#else - dlclose(handle); -#endif - } -} - -static GLADapiproc glad_dlsym_handle(void* handle, const char *name) { - if (handle == NULL) { - return NULL; - } - -#if GLAD_PLATFORM_WIN32 - return (GLADapiproc) GetProcAddress((HMODULE) handle, name); -#else - return GLAD_GNUC_EXTENSION (GLADapiproc) dlsym(handle, name); -#endif -} - -#endif /* GLAD_LOADER_LIBRARY_C_ */ - -typedef void* (GLAD_API_PTR *GLADglxprocaddrfunc)(const char*); - -static GLADapiproc glad_glx_get_proc(void *userptr, const char *name) { - return GLAD_GNUC_EXTENSION ((GLADapiproc (*)(const char *name)) userptr)(name); -} - -static void* _glx_handle; - -static void* glad_glx_dlopen_handle(void) { - static const char *NAMES[] = { -#if defined __CYGWIN__ - "libGL-1.so", -#endif - "libGL.so.1", - "libGL.so" - }; - - if (_glx_handle == NULL) { - _glx_handle = glad_get_dlopen_handle(NAMES, sizeof(NAMES) / sizeof(NAMES[0])); - } - - return _glx_handle; -} - -int gladLoaderLoadGLX(Display *display, int screen) { - int version = 0; - void *handle = NULL; - int did_load = 0; - GLADglxprocaddrfunc loader; - - did_load = _glx_handle == NULL; - handle = glad_glx_dlopen_handle(); - if (handle != NULL) { - loader = (GLADglxprocaddrfunc) glad_dlsym_handle(handle, "glXGetProcAddressARB"); - if (loader != NULL) { - version = gladLoadGLXUserPtr(display, screen, glad_glx_get_proc, GLAD_GNUC_EXTENSION (void*) loader); - } - - if (!version && did_load) { - gladLoaderUnloadGLX(); - } - } - - return version; -} - - -void gladLoaderUnloadGLX() { - if (_glx_handle != NULL) { - glad_close_dlopen_handle(_glx_handle); - _glx_handle = NULL; - } -} - -#endif /* GLAD_GLX */ - -#ifdef __cplusplus -} -#endif diff --git a/core/linux-dist/x11.cpp b/core/linux-dist/x11.cpp index 49f317e2d..f80c1febb 100644 --- a/core/linux-dist/x11.cpp +++ b/core/linux-dist/x11.cpp @@ -278,16 +278,8 @@ void x11_window_create() int depth = CopyFromParent; - XVisualInfo* x11Visual = nullptr; - Colormap x11Colormap = 0; -#if !defined(GLES) - - if (!theGLContext.ChooseVisual(x11_disp, &x11Visual, &depth)) - exit(1); - x11Colormap = XCreateColormap(x11_disp, RootWindow(x11_disp, x11Screen), x11Visual->visual, AllocNone); -#else int i32Depth = DefaultDepth(x11_disp, x11Screen); - x11Visual = new XVisualInfo; + XVisualInfo* x11Visual = new XVisualInfo; if (!XMatchVisualInfo(x11_disp, x11Screen, i32Depth, TrueColor, x11Visual)) { ERROR_LOG(RENDERER, "Error: Unable to acquire visual"); @@ -296,8 +288,8 @@ void x11_window_create() } // Gets the window parameters Window sRootWindow = RootWindow(x11_disp, x11Screen); - x11Colormap = XCreateColormap(x11_disp, sRootWindow, x11Visual->visual, AllocNone); -#endif + Colormap x11Colormap = XCreateColormap(x11_disp, sRootWindow, x11Visual->visual, AllocNone); + XSetWindowAttributes sWA; sWA.colormap = x11Colormap; @@ -326,11 +318,8 @@ void x11_window_create() // Creates the X11 window x11_win = XCreateWindow(x11_disp, RootWindow(x11_disp, x11Screen), 0, 0, x11_width, x11_height, 0, depth, InputOutput, x11Visual->visual, ui32Mask, &sWA); -#if !defined(GLES) - XFree(x11Visual); -#else + delete x11Visual; -#endif XSetWindowBackground(x11_disp, x11_win, 0); diff --git a/core/wsi/egl.cpp b/core/wsi/egl.cpp index d55993381..0ee2672a1 100644 --- a/core/wsi/egl.cpp +++ b/core/wsi/egl.cpp @@ -108,8 +108,8 @@ bool EGLGraphicsContext::init() } if (try_full_gl) { - EGLint contextAttrs[] = { EGL_CONTEXT_MAJOR_VERSION_KHR, 3, - EGL_CONTEXT_OPENGL_PROFILE_MASK_KHR, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT_KHR, + EGLint contextAttrs[] = { EGL_CONTEXT_MAJOR_VERSION, 3, + EGL_CONTEXT_OPENGL_PROFILE_MASK, EGL_CONTEXT_OPENGL_COMPATIBILITY_PROFILE_BIT, EGL_NONE }; context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttrs); if (context != EGL_NO_CONTEXT) diff --git a/core/wsi/gl_context.h b/core/wsi/gl_context.h index a3f085a6c..1858322ef 100644 --- a/core/wsi/gl_context.h +++ b/core/wsi/gl_context.h @@ -88,14 +88,10 @@ private: #include "sdl.h" -#elif defined(GLES) || defined(__ANDROID__) || defined(__SWITCH__) +#elif defined(__ANDROID__) || defined(SUPPORT_DISPMANX) || defined(SUPPORT_X11) #include "egl.h" -#elif defined(SUPPORT_X11) - -#include "xgl.h" - #else #error Unsupported window system diff --git a/core/wsi/xgl.cpp b/core/wsi/xgl.cpp deleted file mode 100644 index 271a87209..000000000 --- a/core/wsi/xgl.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - Created on: Oct 18, 2019 - - Copyright 2019 flyinghead - - This file is part of Flycast. - - Flycast is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - Flycast is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Flycast. If not, see . -*/ -#if defined(SUPPORT_X11) && !defined(USE_SDL) && !defined(LIBRETRO) -#include "cfg/option.h" -#include "types.h" -#include "xgl.h" - -XGLGraphicsContext theGLContext; - -static int x11_error_handler(Display *, XErrorEvent *) -{ - return 0; -} - -bool XGLGraphicsContext::init() -{ - instance = this; - int context_attribs[] = - { - GLX_CONTEXT_MAJOR_VERSION_ARB, 4, - GLX_CONTEXT_MINOR_VERSION_ARB, 3, -#ifndef NDEBUG - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, -#endif - GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - None - }; - int (*old_handler)(Display *, XErrorEvent *) = XSetErrorHandler(&x11_error_handler); - - context = glXCreateContextAttribsARB((Display *)display, *framebufferConfigs, 0, True, context_attribs); - if (!context) - { - INFO_LOG(RENDERER, "OpenGL 4.3 not supported"); - // Try GL 3.0 - context_attribs[1] = 3; - context_attribs[3] = 0; - context = glXCreateContextAttribsARB((Display *)display, *framebufferConfigs, 0, True, context_attribs); - if (!context) - { - ERROR_LOG(RENDERER, "OpenGL 3.0 not supported"); - return false; - } - } - XSetErrorHandler(old_handler); - XSync((Display *)display, False); - - glXMakeCurrent((Display *)display, (GLXDrawable)window, context); - - if (!gladLoadGL((GLADloadfunc) glXGetProcAddressARB)) - return false; - - if (!GLAD_GL_VERSION_3_1) - return false; - - Window win; - int temp; - unsigned int tempu; - XGetGeometry((Display *)display, (GLXDrawable)window, &win, &temp, &temp, (u32 *)&settings.display.width, (u32 *)&settings.display.height, &tempu, &tempu); - - swapOnVSync = config::VSync; - if (glXSwapIntervalMESA != nullptr) - glXSwapIntervalMESA((unsigned)swapOnVSync); - else - { - if (glXSwapIntervalEXT != nullptr) - glXSwapIntervalEXT((Display *)display, (GLXDrawable)window, (int)swapOnVSync); - } - - postInit(); - - return true; -} - -bool XGLGraphicsContext::ChooseVisual(Display* x11Display, XVisualInfo** visual, int* depth) -{ - const long x11Screen = XDefaultScreen(x11Display); - - gladLoaderLoadGLX(x11Display, x11Screen); - - // Get a matching FB config - static int visual_attribs[] = - { - GLX_X_RENDERABLE , True, - GLX_DRAWABLE_TYPE , GLX_WINDOW_BIT, - GLX_RENDER_TYPE , GLX_RGBA_BIT, - GLX_X_VISUAL_TYPE , GLX_TRUE_COLOR, - GLX_RED_SIZE , 8, - GLX_GREEN_SIZE , 8, - GLX_BLUE_SIZE , 8, - GLX_ALPHA_SIZE , 8, - GLX_DOUBLEBUFFER , True, - None - }; - - int glx_major, glx_minor; - - // FBConfigs were added in GLX version 1.3. - if (!glXQueryVersion(x11Display, &glx_major, &glx_minor) || - ((glx_major == 1) && (glx_minor < 3)) || (glx_major < 1)) - { - ERROR_LOG(RENDERER, "Invalid GLX version"); - return false; - } - - int fbcount; - framebufferConfigs = glXChooseFBConfig(x11Display, x11Screen, visual_attribs, &fbcount); - if (framebufferConfigs == nullptr) - { - ERROR_LOG(RENDERER, "Failed to retrieve a framebuffer config"); - return false; - } - INFO_LOG(RENDERER, "Found %d matching FB configs.", fbcount); - - // Get a visual - XVisualInfo *vi = glXGetVisualFromFBConfig(x11Display, *framebufferConfigs); - INFO_LOG(RENDERER, "Chosen visual ID = 0x%lx", vi->visualid); - - *depth = vi->depth; - *visual = vi; - - return true; -} - -void XGLGraphicsContext::swap() -{ - do_swap_automation(); - if (swapOnVSync == (settings.input.fastForwardMode || !config::VSync)) - { - swapOnVSync = (!settings.input.fastForwardMode && config::VSync); - if (glXSwapIntervalMESA != nullptr) - glXSwapIntervalMESA((unsigned)swapOnVSync); - else if (glXSwapIntervalEXT != nullptr) - glXSwapIntervalEXT((Display *)display, (GLXDrawable)window, (int)swapOnVSync); - } - glXSwapBuffers((Display *)display, (GLXDrawable)window); - - Window win; - int temp; - unsigned int tempu; - XGetGeometry((Display *)display, (GLXDrawable)window, &win, &temp, &temp, (u32 *)&settings.display.width, (u32 *)&settings.display.height, &tempu, &tempu); -} - -void XGLGraphicsContext::term() -{ - preTerm(); - if (context) - { - glXMakeCurrent((Display *)display, None, NULL); - glXDestroyContext((Display *)display, context); - context = (GLXContext)0; - } -} - -#endif diff --git a/core/wsi/xgl.h b/core/wsi/xgl.h deleted file mode 100644 index 101a039ad..000000000 --- a/core/wsi/xgl.h +++ /dev/null @@ -1,41 +0,0 @@ -/* - Created on: Oct 18, 2019 - - Copyright 2019 flyinghead - - This file is part of Flycast. - - Flycast is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - Flycast is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Flycast. If not, see . -*/ -#pragma once -#include "gl_context.h" -#include - -class XGLGraphicsContext : public GLGraphicsContext -{ -public: - ~XGLGraphicsContext() override { term(); XFree(framebufferConfigs); } - - bool init(); - void term() override; - void swap(); - bool ChooseVisual(Display* x11Display, XVisualInfo** visual, int* depth); - -private: - GLXContext context; - GLXFBConfig* framebufferConfigs = nullptr; - bool swapOnVSync = false; -}; - -extern XGLGraphicsContext theGLContext; From 0c5e79d618753a7c7ac33fd7eeae76e53ab77016 Mon Sep 17 00:00:00 2001 From: scribam Date: Sat, 17 Feb 2024 14:49:21 +0100 Subject: [PATCH 12/86] deps: update imgui to version 1.90.3 --- core/deps/imgui/backends/imgui_impl_dx9.cpp | 28 +- .../deps/imgui/backends/imgui_impl_vulkan.cpp | 146 ++-- core/deps/imgui/backends/imgui_impl_vulkan.h | 36 +- core/deps/imgui/imgui.cpp | 715 ++++++++++++------ core/deps/imgui/imgui.h | 35 +- core/deps/imgui/imgui_demo.cpp | 10 +- core/deps/imgui/imgui_draw.cpp | 4 +- core/deps/imgui/imgui_internal.h | 283 ++++--- core/deps/imgui/imgui_tables.cpp | 4 +- core/deps/imgui/imgui_widgets.cpp | 99 ++- core/rend/vulkan/vulkan_context.cpp | 3 +- 11 files changed, 865 insertions(+), 498 deletions(-) diff --git a/core/deps/imgui/backends/imgui_impl_dx9.cpp b/core/deps/imgui/backends/imgui_impl_dx9.cpp index 5a2ef2f49..fcda7b4f7 100644 --- a/core/deps/imgui/backends/imgui_impl_dx9.cpp +++ b/core/deps/imgui/backends/imgui_impl_dx9.cpp @@ -15,6 +15,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575) // 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. // 2021-06-29: Reorganized backend to pull data from a single structure to facilitate usage with multiple-contexts (all g_XXXX access changed to bd->XXXX). // 2021-06-25: DirectX9: Explicitly disable texture state stages after >= 1. @@ -295,6 +296,24 @@ void ImGui_ImplDX9_Shutdown() IM_DELETE(bd); } +static bool ImGui_ImplDX9_CheckFormatSupport(IDirect3DDevice9* pDevice, D3DFORMAT format) +{ + IDirect3D9* pd3d = nullptr; + if (pDevice->GetDirect3D(&pd3d) != D3D_OK) + return false; + D3DDEVICE_CREATION_PARAMETERS param = {}; + D3DDISPLAYMODE mode = {}; + if (pDevice->GetCreationParameters(¶m) != D3D_OK || pDevice->GetDisplayMode(0, &mode) != D3D_OK) + { + pd3d->Release(); + return false; + } + // Font texture should support linear filter, color blend and write to render-target + bool support = (pd3d->CheckDeviceFormat(param.AdapterOrdinal, param.DeviceType, mode.Format, D3DUSAGE_DYNAMIC | D3DUSAGE_QUERY_FILTER | D3DUSAGE_QUERY_POSTPIXELSHADER_BLENDING, D3DRTYPE_TEXTURE, format)) == D3D_OK; + pd3d->Release(); + return support; +} + static bool ImGui_ImplDX9_CreateFontsTexture() { // Build texture atlas @@ -306,18 +325,21 @@ static bool ImGui_ImplDX9_CreateFontsTexture() // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) #ifndef IMGUI_USE_BGRA_PACKED_COLOR - if (io.Fonts->TexPixelsUseColors) + const bool rgba_support = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8); + if (!rgba_support && io.Fonts->TexPixelsUseColors) { ImU32* dst_start = (ImU32*)ImGui::MemAlloc((size_t)width * height * bytes_per_pixel); for (ImU32* src = (ImU32*)pixels, *dst = dst_start, *dst_end = dst_start + (size_t)width * height; dst < dst_end; src++, dst++) *dst = IMGUI_COL_TO_DX9_ARGB(*src); pixels = (unsigned char*)dst_start; } +#else + const bool rgba_support = false; #endif // Upload texture to graphics system bd->FontTexture = nullptr; - if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0) + if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, rgba_support ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0) return false; D3DLOCKED_RECT tex_locked_rect; if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK) @@ -330,7 +352,7 @@ static bool ImGui_ImplDX9_CreateFontsTexture() io.Fonts->SetTexID((ImTextureID)bd->FontTexture); #ifndef IMGUI_USE_BGRA_PACKED_COLOR - if (io.Fonts->TexPixelsUseColors) + if (!rgba_support && io.Fonts->TexPixelsUseColors) ImGui::MemFree(pixels); #endif diff --git a/core/deps/imgui/backends/imgui_impl_vulkan.cpp b/core/deps/imgui/backends/imgui_impl_vulkan.cpp index d84dda574..62acf10b0 100644 --- a/core/deps/imgui/backends/imgui_impl_vulkan.cpp +++ b/core/deps/imgui/backends/imgui_impl_vulkan.cpp @@ -33,8 +33,12 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2024-02-14: *BREAKING CHANGE*: Moved RenderPass parameter from ImGui_ImplVulkan_Init() function to ImGui_ImplVulkan_InitInfo structure. Not required when using dynamic rendering. +// 2024-02-12: *BREAKING CHANGE*: Dynamic rendering now require filling PipelineRenderingCreateInfo structure. +// 2024-01-19: Vulkan: Fixed vkAcquireNextImageKHR() validation errors in VulkanSDK 1.3.275 by allocating one extra semaphore than in-flight frames. (#7236) +// 2024-01-11: Vulkan: Fixed vkMapMemory() calls unnecessarily using full buffer size (#3957). Fixed MinAllocationSize handing (#7189). // 2024-01-03: Vulkan: Added MinAllocationSize field in ImGui_ImplVulkan_InitInfo to workaround zealous "best practice" validation layer. (#7189, #4238) -// 2024-01-03: Vulkan: Stoped creating command pools with VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT as we don't reset them. +// 2024-01-03: Vulkan: Stopped creating command pools with VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT as we don't reset them. // 2023-11-29: Vulkan: Fixed mismatching allocator passed to vkCreateCommandPool() vs vkDestroyCommandPool(). (#7075) // 2023-11-10: *BREAKING CHANGE*: Removed parameter from ImGui_ImplVulkan_CreateFontsTexture(): backend now creates its own command-buffer to upload fonts. // *BREAKING CHANGE*: Removed ImGui_ImplVulkan_DestroyFontUploadObjects() which is now unecessary as we create and destroy those objects in the backend. @@ -91,14 +95,14 @@ #endif // Forward Declarations -struct ImGui_ImplVulkanH_FrameRenderBuffers; -struct ImGui_ImplVulkanH_WindowRenderBuffers; +struct ImGui_ImplVulkan_FrameRenderBuffers; +struct ImGui_ImplVulkan_WindowRenderBuffers; bool ImGui_ImplVulkan_CreateDeviceObjects(); void ImGui_ImplVulkan_DestroyDeviceObjects(); +void ImGui_ImplVulkan_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkan_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkan_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkan_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator); -void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator); -void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count); void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator); @@ -182,15 +186,14 @@ IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_DEF) #undef IMGUI_VULKAN_FUNC_DEF #endif // VK_NO_PROTOTYPES -#if defined(VK_VERSION_1_3) || defined(VK_KHR_dynamic_rendering) -#define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING +#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING static PFN_vkCmdBeginRenderingKHR ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR; static PFN_vkCmdEndRenderingKHR ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR; #endif // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() // [Please zero-clear before use!] -struct ImGui_ImplVulkanH_FrameRenderBuffers +struct ImGui_ImplVulkan_FrameRenderBuffers { VkDeviceMemory VertexBufferMemory; VkDeviceMemory IndexBufferMemory; @@ -202,24 +205,22 @@ struct ImGui_ImplVulkanH_FrameRenderBuffers // Each viewport will hold 1 ImGui_ImplVulkanH_WindowRenderBuffers // [Please zero-clear before use!] -struct ImGui_ImplVulkanH_WindowRenderBuffers +struct ImGui_ImplVulkan_WindowRenderBuffers { uint32_t Index; uint32_t Count; - ImGui_ImplVulkanH_FrameRenderBuffers* FrameRenderBuffers; + ImGui_ImplVulkan_FrameRenderBuffers* FrameRenderBuffers; }; // Vulkan data struct ImGui_ImplVulkan_Data { ImGui_ImplVulkan_InitInfo VulkanInitInfo; - VkRenderPass RenderPass; VkDeviceSize BufferMemoryAlignment; VkPipelineCreateFlags PipelineCreateFlags; VkDescriptorSetLayout DescriptorSetLayout; VkPipelineLayout PipelineLayout; VkPipeline Pipeline; - uint32_t Subpass; VkShaderModule ShaderModuleVert; VkShaderModule ShaderModuleFrag; @@ -233,7 +234,7 @@ struct ImGui_ImplVulkan_Data VkCommandBuffer FontCommandBuffer; // Render buffers for main window - ImGui_ImplVulkanH_WindowRenderBuffers MainWindowRenderBuffers; + ImGui_ImplVulkan_WindowRenderBuffers MainWindowRenderBuffers; ImGui_ImplVulkan_Data() { @@ -385,7 +386,13 @@ static void check_vk_result(VkResult err) v->CheckVkResultFn(err); } -static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory, VkDeviceSize& p_buffer_size, size_t new_size, VkBufferUsageFlagBits usage) +// Same as IM_MEMALIGN(). 'alignment' must be a power of two. +static inline VkDeviceSize AlignBufferSize(VkDeviceSize size, VkDeviceSize alignment) +{ + return (size + alignment - 1) & ~(alignment - 1); +} + +static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory, VkDeviceSize& buffer_size, size_t new_size, VkBufferUsageFlagBits usage) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; @@ -395,10 +402,10 @@ static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory if (buffer_memory != VK_NULL_HANDLE) vkFreeMemory(v->Device, buffer_memory, v->Allocator); - VkDeviceSize vertex_buffer_size_aligned = ((new_size - 1) / bd->BufferMemoryAlignment + 1) * bd->BufferMemoryAlignment; + VkDeviceSize buffer_size_aligned = AlignBufferSize(IM_MAX(v->MinAllocationSize, new_size), bd->BufferMemoryAlignment); VkBufferCreateInfo buffer_info = {}; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - buffer_info.size = vertex_buffer_size_aligned; + buffer_info.size = buffer_size_aligned; buffer_info.usage = usage; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; err = vkCreateBuffer(v->Device, &buffer_info, v->Allocator, &buffer); @@ -407,20 +414,19 @@ static void CreateOrResizeBuffer(VkBuffer& buffer, VkDeviceMemory& buffer_memory VkMemoryRequirements req; vkGetBufferMemoryRequirements(v->Device, buffer, &req); bd->BufferMemoryAlignment = (bd->BufferMemoryAlignment > req.alignment) ? bd->BufferMemoryAlignment : req.alignment; - VkDeviceSize size = IM_MAX(v->MinAllocationSize, req.size); VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - alloc_info.allocationSize = size; + alloc_info.allocationSize = req.size; alloc_info.memoryTypeIndex = ImGui_ImplVulkan_MemoryType(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, req.memoryTypeBits); err = vkAllocateMemory(v->Device, &alloc_info, v->Allocator, &buffer_memory); check_vk_result(err); err = vkBindBufferMemory(v->Device, buffer, buffer_memory, 0); check_vk_result(err); - p_buffer_size = size; + buffer_size = buffer_size_aligned; } -static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline pipeline, VkCommandBuffer command_buffer, ImGui_ImplVulkanH_FrameRenderBuffers* rb, int fb_width, int fb_height) +static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline pipeline, VkCommandBuffer command_buffer, ImGui_ImplVulkan_FrameRenderBuffers* rb, int fb_width, int fb_height) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); @@ -479,23 +485,23 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm pipeline = bd->Pipeline; // Allocate array to store enough vertex/index buffers - ImGui_ImplVulkanH_WindowRenderBuffers* wrb = &bd->MainWindowRenderBuffers; + ImGui_ImplVulkan_WindowRenderBuffers* wrb = &bd->MainWindowRenderBuffers; if (wrb->FrameRenderBuffers == nullptr) { wrb->Index = 0; wrb->Count = v->ImageCount; - wrb->FrameRenderBuffers = (ImGui_ImplVulkanH_FrameRenderBuffers*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameRenderBuffers) * wrb->Count); - memset(wrb->FrameRenderBuffers, 0, sizeof(ImGui_ImplVulkanH_FrameRenderBuffers) * wrb->Count); + wrb->FrameRenderBuffers = (ImGui_ImplVulkan_FrameRenderBuffers*)IM_ALLOC(sizeof(ImGui_ImplVulkan_FrameRenderBuffers) * wrb->Count); + memset(wrb->FrameRenderBuffers, 0, sizeof(ImGui_ImplVulkan_FrameRenderBuffers) * wrb->Count); } IM_ASSERT(wrb->Count == v->ImageCount); wrb->Index = (wrb->Index + 1) % wrb->Count; - ImGui_ImplVulkanH_FrameRenderBuffers* rb = &wrb->FrameRenderBuffers[wrb->Index]; + ImGui_ImplVulkan_FrameRenderBuffers* rb = &wrb->FrameRenderBuffers[wrb->Index]; if (draw_data->TotalVtxCount > 0) { // Create or resize the vertex/index buffers - size_t vertex_size = draw_data->TotalVtxCount * sizeof(ImDrawVert); - size_t index_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx); + size_t vertex_size = AlignBufferSize(draw_data->TotalVtxCount * sizeof(ImDrawVert), bd->BufferMemoryAlignment); + size_t index_size = AlignBufferSize(draw_data->TotalIdxCount * sizeof(ImDrawIdx), bd->BufferMemoryAlignment); if (vertex_size != 0) vertex_size = ((vertex_size - 1) / bd->BufferMemoryAlignment + 1) * bd->BufferMemoryAlignment; if (index_size != 0) @@ -509,9 +515,9 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Upload vertex/index data into a single contiguous GPU buffer ImDrawVert* vtx_dst = nullptr; ImDrawIdx* idx_dst = nullptr; - VkResult err = vkMapMemory(v->Device, rb->VertexBufferMemory, 0, rb->VertexBufferSize, 0, (void**)&vtx_dst); + VkResult err = vkMapMemory(v->Device, rb->VertexBufferMemory, 0, vertex_size, 0, (void**)&vtx_dst); check_vk_result(err); - err = vkMapMemory(v->Device, rb->IndexBufferMemory, 0, rb->IndexBufferSize, 0, (void**)&idx_dst); + err = vkMapMemory(v->Device, rb->IndexBufferMemory, 0, index_size, 0, (void**)&idx_dst); check_vk_result(err); for (int n = 0; n < draw_data->CmdListsCount; n++) { @@ -951,13 +957,11 @@ static void ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAllocationC info.subpass = subpass; #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING - VkPipelineRenderingCreateInfoKHR pipelineRenderingCreateInfo = {}; - pipelineRenderingCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR; - pipelineRenderingCreateInfo.colorAttachmentCount = 1; - pipelineRenderingCreateInfo.pColorAttachmentFormats = &bd->VulkanInitInfo.ColorAttachmentFormat; if (bd->VulkanInitInfo.UseDynamicRendering) { - info.pNext = &pipelineRenderingCreateInfo; + IM_ASSERT(bd->VulkanInitInfo.PipelineRenderingCreateInfo.sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR && "PipelineRenderingCreateInfo sType must be VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR"); + IM_ASSERT(bd->VulkanInitInfo.PipelineRenderingCreateInfo.pNext == nullptr && "PipelineRenderingCreateInfo pNext must be NULL"); + info.pNext = &bd->VulkanInitInfo.PipelineRenderingCreateInfo; info.renderPass = VK_NULL_HANDLE; // Just make sure it's actually nullptr. } #endif @@ -1022,7 +1026,7 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() check_vk_result(err); } - ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, bd->RenderPass, v->MSAASamples, &bd->Pipeline, bd->Subpass); + ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, v->RenderPass, v->MSAASamples, &bd->Pipeline, v->Subpass); return true; } @@ -1031,7 +1035,7 @@ void ImGui_ImplVulkan_DestroyDeviceObjects() { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; - ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &bd->MainWindowRenderBuffers, v->Allocator); + ImGui_ImplVulkan_DestroyWindowRenderBuffers(v->Device, &bd->MainWindowRenderBuffers, v->Allocator); ImGui_ImplVulkan_DestroyFontsTexture(); if (bd->FontCommandBuffer) { vkFreeCommandBuffers(v->Device, bd->FontCommandPool, 1, &bd->FontCommandBuffer); bd->FontCommandBuffer = VK_NULL_HANDLE; } @@ -1072,7 +1076,7 @@ bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const ch return true; } -bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass) +bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) { IM_ASSERT(g_FunctionsLoaded && "Need to call ImGui_ImplVulkan_LoadFunctions() if IMGUI_IMPL_VULKAN_NO_PROTOTYPES or VK_NO_PROTOTYPES are set!"); @@ -1107,11 +1111,9 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass rend IM_ASSERT(info->MinImageCount >= 2); IM_ASSERT(info->ImageCount >= info->MinImageCount); if (info->UseDynamicRendering == false) - IM_ASSERT(render_pass != VK_NULL_HANDLE); + IM_ASSERT(info->RenderPass != VK_NULL_HANDLE); bd->VulkanInitInfo = *info; - bd->RenderPass = render_pass; - bd->Subpass = info->Subpass; ImGui_ImplVulkan_CreateDeviceObjects(); @@ -1150,7 +1152,7 @@ void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; VkResult err = vkDeviceWaitIdle(v->Device); check_vk_result(err); - ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &bd->MainWindowRenderBuffers, v->Allocator); + ImGui_ImplVulkan_DestroyWindowRenderBuffers(v->Device, &bd->MainWindowRenderBuffers, v->Allocator); bd->VulkanInitInfo.MinImageCount = min_image_count; } @@ -1197,6 +1199,26 @@ void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set) vkFreeDescriptorSets(v->Device, v->DescriptorPool, 1, &descriptor_set); } +void ImGui_ImplVulkan_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkan_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator) +{ + if (buffers->VertexBuffer) { vkDestroyBuffer(device, buffers->VertexBuffer, allocator); buffers->VertexBuffer = VK_NULL_HANDLE; } + if (buffers->VertexBufferMemory) { vkFreeMemory(device, buffers->VertexBufferMemory, allocator); buffers->VertexBufferMemory = VK_NULL_HANDLE; } + if (buffers->IndexBuffer) { vkDestroyBuffer(device, buffers->IndexBuffer, allocator); buffers->IndexBuffer = VK_NULL_HANDLE; } + if (buffers->IndexBufferMemory) { vkFreeMemory(device, buffers->IndexBufferMemory, allocator); buffers->IndexBufferMemory = VK_NULL_HANDLE; } + buffers->VertexBufferSize = 0; + buffers->IndexBufferSize = 0; +} + +void ImGui_ImplVulkan_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkan_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator) +{ + for (uint32_t n = 0; n < buffers->Count; n++) + ImGui_ImplVulkan_DestroyFrameRenderBuffers(device, &buffers->FrameRenderBuffers[n], allocator); + IM_FREE(buffers->FrameRenderBuffers); + buffers->FrameRenderBuffers = nullptr; + buffers->Index = 0; + buffers->Count = 0; +} + //------------------------------------------------------------------------- // Internal / Miscellaneous Vulkan Helpers // (Used by example's main.cpp. Used by multi-viewport features. PROBABLY NOT used by your own app.) @@ -1284,15 +1306,13 @@ VkPresentModeKHR ImGui_ImplVulkanH_SelectPresentMode(VkPhysicalDevice physical_d void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator) { IM_ASSERT(physical_device != VK_NULL_HANDLE && device != VK_NULL_HANDLE); - (void)physical_device; - (void)allocator; + IM_UNUSED(physical_device); // Create Command Buffers VkResult err; for (uint32_t i = 0; i < wd->ImageCount; i++) { ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i]; - ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[i]; { VkCommandPoolCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -1317,6 +1337,11 @@ void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_devi err = vkCreateFence(device, &info, allocator, &fd->Fence); check_vk_result(err); } + } + + for (uint32_t i = 0; i < wd->SemaphoreCount; i++) + { + ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[i]; { VkSemaphoreCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; @@ -1352,10 +1377,9 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V // We don't use ImGui_ImplVulkanH_DestroyWindow() because we want to preserve the old swapchain to create the new one. // Destroy old Framebuffer for (uint32_t i = 0; i < wd->ImageCount; i++) - { ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); + for (uint32_t i = 0; i < wd->SemaphoreCount; i++) ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); - } IM_FREE(wd->Frames); IM_FREE(wd->FrameSemaphores); wd->Frames = nullptr; @@ -1414,11 +1438,12 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V err = vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, backbuffers); check_vk_result(err); - IM_ASSERT(wd->Frames == nullptr); + IM_ASSERT(wd->Frames == nullptr && wd->FrameSemaphores == nullptr); + wd->SemaphoreCount = wd->ImageCount + 1; wd->Frames = (ImGui_ImplVulkanH_Frame*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_Frame) * wd->ImageCount); - wd->FrameSemaphores = (ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->ImageCount); + wd->FrameSemaphores = (ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->SemaphoreCount); memset(wd->Frames, 0, sizeof(wd->Frames[0]) * wd->ImageCount); - memset(wd->FrameSemaphores, 0, sizeof(wd->FrameSemaphores[0]) * wd->ImageCount); + memset(wd->FrameSemaphores, 0, sizeof(wd->FrameSemaphores[0]) * wd->SemaphoreCount); for (uint32_t i = 0; i < wd->ImageCount; i++) wd->Frames[i].Backbuffer = backbuffers[i]; } @@ -1464,7 +1489,7 @@ void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, V // We do not create a pipeline by default as this is also used by examples' main.cpp, // but secondary viewport in multi-viewport mode may want to create one with: - //ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline, bd->Subpass); + //ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline, v->Subpass); } // Create The Image Views @@ -1525,10 +1550,9 @@ void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui //vkQueueWaitIdle(bd->Queue); for (uint32_t i = 0; i < wd->ImageCount; i++) - { ImGui_ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator); + for (uint32_t i = 0; i < wd->SemaphoreCount; i++) ImGui_ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator); - } IM_FREE(wd->Frames); IM_FREE(wd->FrameSemaphores); wd->Frames = nullptr; @@ -1561,26 +1585,6 @@ void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH fsd->ImageAcquiredSemaphore = fsd->RenderCompleteSemaphore = VK_NULL_HANDLE; } -void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator) -{ - if (buffers->VertexBuffer) { vkDestroyBuffer(device, buffers->VertexBuffer, allocator); buffers->VertexBuffer = VK_NULL_HANDLE; } - if (buffers->VertexBufferMemory) { vkFreeMemory(device, buffers->VertexBufferMemory, allocator); buffers->VertexBufferMemory = VK_NULL_HANDLE; } - if (buffers->IndexBuffer) { vkDestroyBuffer(device, buffers->IndexBuffer, allocator); buffers->IndexBuffer = VK_NULL_HANDLE; } - if (buffers->IndexBufferMemory) { vkFreeMemory(device, buffers->IndexBufferMemory, allocator); buffers->IndexBufferMemory = VK_NULL_HANDLE; } - buffers->VertexBufferSize = 0; - buffers->IndexBufferSize = 0; -} - -void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator) -{ - for (uint32_t n = 0; n < buffers->Count; n++) - ImGui_ImplVulkanH_DestroyFrameRenderBuffers(device, &buffers->FrameRenderBuffers[n], allocator); - IM_FREE(buffers->FrameRenderBuffers); - buffers->FrameRenderBuffers = nullptr; - buffers->Index = 0; - buffers->Count = 0; -} - //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE diff --git a/core/deps/imgui/backends/imgui_impl_vulkan.h b/core/deps/imgui/backends/imgui_impl_vulkan.h index 490fbb040..f77fc235b 100644 --- a/core/deps/imgui/backends/imgui_impl_vulkan.h +++ b/core/deps/imgui/backends/imgui_impl_vulkan.h @@ -46,9 +46,20 @@ #if defined(IMGUI_IMPL_VULKAN_NO_PROTOTYPES) && !defined(VK_NO_PROTOTYPES) #define VK_NO_PROTOTYPES #endif +#if defined(VK_USE_PLATFORM_WIN32_KHR) && !defined(NOMINMAX) +#define NOMINMAX #include +#else +#include +#endif +#if defined(VK_VERSION_1_3) || defined(VK_KHR_dynamic_rendering) +#define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING +#endif // Initialization data, for ImGui_ImplVulkan_Init() +// - VkDescriptorPool should be created with VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, +// and must contain a pool size large enough to hold an ImGui VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER descriptor. +// - When using dynamic rendering, set UseDynamicRendering=true and fill PipelineRenderingCreateInfo structure. // [Please zero-clear before use!] struct ImGui_ImplVulkan_InitInfo { @@ -57,25 +68,31 @@ struct ImGui_ImplVulkan_InitInfo VkDevice Device; uint32_t QueueFamily; VkQueue Queue; + VkDescriptorPool DescriptorPool; // See requirements in note above + VkRenderPass RenderPass; // Ignored if using dynamic rendering + uint32_t MinImageCount; // >= 2 + uint32_t ImageCount; // >= MinImageCount + VkSampleCountFlagBits MSAASamples; // 0 defaults to VK_SAMPLE_COUNT_1_BIT + + // (Optional) VkPipelineCache PipelineCache; - VkDescriptorPool DescriptorPool; uint32_t Subpass; - uint32_t MinImageCount; // >= 2 - uint32_t ImageCount; // >= MinImageCount - VkSampleCountFlagBits MSAASamples; // >= VK_SAMPLE_COUNT_1_BIT (0 -> default to VK_SAMPLE_COUNT_1_BIT) - // Dynamic Rendering (Optional) - bool UseDynamicRendering; // Need to explicitly enable VK_KHR_dynamic_rendering extension to use this, even for Vulkan 1.3. - VkFormat ColorAttachmentFormat; // Required for dynamic rendering + // (Optional) Dynamic Rendering + // Need to explicitly enable VK_KHR_dynamic_rendering extension to use this, even for Vulkan 1.3. + bool UseDynamicRendering; +#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING + VkPipelineRenderingCreateInfoKHR PipelineRenderingCreateInfo; +#endif - // Allocation, Debugging + // (Optional) Allocation, Debugging const VkAllocationCallbacks* Allocator; void (*CheckVkResultFn)(VkResult err); VkDeviceSize MinAllocationSize; // Minimum allocation size. Set to 1024*1024 to satisfy zealous best practices validation layer and waste a little memory. }; // Called by user code -IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass render_pass); +IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info); IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(); IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE); @@ -155,6 +172,7 @@ struct ImGui_ImplVulkanH_Window VkClearValue ClearValue; uint32_t FrameIndex; // Current frame being rendered to (0 <= FrameIndex < FrameInFlightCount) uint32_t ImageCount; // Number of simultaneous in-flight frames (returned by vkGetSwapchainImagesKHR, usually derived from min_image_count) + uint32_t SemaphoreCount; // Number of simultaneous in-flight frames + 1, to be able to use it in vkAcquireNextImageKHR uint32_t SemaphoreIndex; // Current set of swapchain wait semaphores we're using (needs to be distinct from per frame data) ImGui_ImplVulkanH_Frame* Frames; ImGui_ImplVulkanH_FrameSemaphores* FrameSemaphores; diff --git a/core/deps/imgui/imgui.cpp b/core/deps/imgui/imgui.cpp index 4c2799b7a..51cfac372 100644 --- a/core/deps/imgui/imgui.cpp +++ b/core/deps/imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.1 +// dear imgui, v1.90.3 // (main code and documentation) // Help: @@ -75,6 +75,7 @@ CODE // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) // [SECTION] INPUTS // [SECTION] ERROR CHECKING +// [SECTION] ITEM SUBMISSION // [SECTION] LAYOUT // [SECTION] SCROLLING // [SECTION] TOOLTIPS @@ -424,6 +425,7 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2024/01/15 (1.90.2) - commented out obsolete ImGuiIO::ImeWindowHandle marked obsolete in 1.87, favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'. - 2023/12/19 (1.90.1) - commented out obsolete ImGuiKey_KeyPadEnter redirection to ImGuiKey_KeypadEnter. - 2023/11/06 (1.90.1) - removed CalcListClipping() marked obsolete in 1.86. Prefer using ImGuiListClipper which can return non-contiguous ranges. - 2023/11/05 (1.90.1) - imgui_freetype: commented out ImGuiFreeType::BuildFontAtlas() obsoleted in 1.81. prefer using #define IMGUI_ENABLE_FREETYPE or see commented code for manual calls. @@ -1045,6 +1047,8 @@ CODE static const float NAV_WINDOWING_HIGHLIGHT_DELAY = 0.20f; // Time before the highlight and screen dimming starts fading in static const float NAV_WINDOWING_LIST_APPEAR_DELAY = 0.15f; // Time before the window list starts to appear +static const float NAV_ACTIVATE_HIGHLIGHT_TIMER = 0.10f; // Time to highlight an item activated by a shortcut. + // Window resizing from edges (when io.ConfigWindowsResizeFromEdges = true and ImGuiBackendFlags_HasMouseCursors is set in io.BackendFlags by backend) static const float WINDOWS_HOVER_PADDING = 4.0f; // Extend outside window for hovering/resizing (maxxed with TouchPadding) and inside windows for borders. Affect FindHoveredWindow(). static const float WINDOWS_RESIZE_FROM_EDGES_FEEDBACK_TIMER = 0.04f; // Reduce visual noise by only highlighting the border after a certain time. @@ -1122,6 +1126,7 @@ static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, static void RenderDimmedBackgrounds(); // Viewports +const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter. static void UpdateViewportsNewFrame(); } @@ -2058,12 +2063,18 @@ ImFileHandle ImFileOpen(const char* filename, const char* mode) // Previously we used ImTextCountCharsFromUtf8/ImTextStrFromUtf8 here but we now need to support ImWchar16 and ImWchar32! const int filename_wsize = ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, NULL, 0); const int mode_wsize = ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, NULL, 0); - ImGuiContext& g = *GImGui; - g.TempBuffer.reserve((filename_wsize + mode_wsize) * sizeof(wchar_t)); - wchar_t* buf = (wchar_t*)(void*)g.TempBuffer.Data; - ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, (wchar_t*)&buf[0], filename_wsize); - ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, (wchar_t*)&buf[filename_wsize], mode_wsize); - return ::_wfopen((const wchar_t*)&buf[0], (const wchar_t*)&buf[filename_wsize]); + + // Use stack buffer if possible, otherwise heap buffer. Sizes include zero terminator. + // We don't rely on current ImGuiContext as this is implied to be a helper function which doesn't depend on it (see #7314). + wchar_t local_temp_stack[FILENAME_MAX]; + ImVector local_temp_heap; + if (filename_wsize + mode_wsize > IM_ARRAYSIZE(local_temp_stack)) + local_temp_heap.resize(filename_wsize + mode_wsize); + wchar_t* filename_wbuf = local_temp_heap.Data ? local_temp_heap.Data : local_temp_stack; + wchar_t* mode_wbuf = filename_wbuf + filename_wsize; + ::MultiByteToWideChar(CP_UTF8, 0, filename, -1, filename_wbuf, filename_wsize); + ::MultiByteToWideChar(CP_UTF8, 0, mode, -1, mode_wbuf, mode_wsize); + return ::_wfopen(filename_wbuf, mode_wbuf); #else return fopen(filename, mode); #endif @@ -3074,7 +3085,7 @@ void ImGui::PopStyleColor(int count) ImGuiContext& g = *GImGui; if (g.ColorStack.Size < count) { - IM_ASSERT_USER_ERROR(g.ColorStack.Size > count, "Calling PopStyleColor() too many times: stack underflow."); + IM_ASSERT_USER_ERROR(g.ColorStack.Size > count, "Calling PopStyleColor() too many times!"); count = g.ColorStack.Size; } while (count > 0) @@ -3137,7 +3148,7 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, float val) *pvar = val; return; } - IM_ASSERT_USER_ERROR(0, "Called PushStyleVar() variant with wrong type!"); + IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); } void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) @@ -3151,7 +3162,7 @@ void ImGui::PushStyleVar(ImGuiStyleVar idx, const ImVec2& val) *pvar = val; return; } - IM_ASSERT_USER_ERROR(0, "Called PushStyleVar() variant with wrong type!"); + IM_ASSERT_USER_ERROR(0, "Calling PushStyleVar() variant with wrong type!"); } void ImGui::PopStyleVar(int count) @@ -3159,7 +3170,7 @@ void ImGui::PopStyleVar(int count) ImGuiContext& g = *GImGui; if (g.StyleVarStack.Size < count) { - IM_ASSERT_USER_ERROR(g.StyleVarStack.Size > count, "Calling PopStyleVar() too many times: stack underflow."); + IM_ASSERT_USER_ERROR(g.StyleVarStack.Size > count, "Calling PopStyleVar() too many times!"); count = g.StyleVarStack.Size; } while (count > 0) @@ -3448,22 +3459,22 @@ void ImGui::RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFl float rounding = (flags & ImGuiNavHighlightFlags_NoRounding) ? 0.0f : g.Style.FrameRounding; ImRect display_rect = bb; display_rect.ClipWith(window->ClipRect); - if (flags & ImGuiNavHighlightFlags_TypeDefault) + const float thickness = 2.0f; + if (flags & ImGuiNavHighlightFlags_Compact) { - const float THICKNESS = 2.0f; - const float DISTANCE = 3.0f + THICKNESS * 0.5f; - display_rect.Expand(ImVec2(DISTANCE, DISTANCE)); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, thickness); + } + else + { + const float distance = 3.0f + thickness * 0.5f; + display_rect.Expand(ImVec2(distance, distance)); bool fully_visible = window->ClipRect.Contains(display_rect); if (!fully_visible) window->DrawList->PushClipRect(display_rect.Min, display_rect.Max); - window->DrawList->AddRect(display_rect.Min + ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), display_rect.Max - ImVec2(THICKNESS * 0.5f, THICKNESS * 0.5f), GetColorU32(ImGuiCol_NavHighlight), rounding, 0, THICKNESS); + window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, thickness); if (!fully_visible) window->DrawList->PopClipRect(); } - if (flags & ImGuiNavHighlightFlags_TypeThin) - { - window->DrawList->AddRect(display_rect.Min, display_rect.Max, GetColorU32(ImGuiCol_NavHighlight), rounding, 0, 1.0f); - } } void ImGui::RenderMouseCursor(ImVec2 base_pos, float base_scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow) @@ -3592,9 +3603,18 @@ void ImGui::Initialize() // Create default viewport ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); + viewport->ID = IMGUI_VIEWPORT_DEFAULT_ID; g.Viewports.push_back(viewport); g.TempBuffer.resize(1024 * 3 + 1, 0); + // Build KeysMayBeCharInput[] lookup table (1 bool per named key) + for (ImGuiKey key = ImGuiKey_NamedKey_BEGIN; key < ImGuiKey_NamedKey_END; key = (ImGuiKey)(key + 1)) + if ((key >= ImGuiKey_0 && key <= ImGuiKey_9) || (key >= ImGuiKey_A && key <= ImGuiKey_Z) || (key >= ImGuiKey_Keypad0 && key <= ImGuiKey_Keypad9) + || key == ImGuiKey_Tab || key == ImGuiKey_Space || key == ImGuiKey_Apostrophe || key == ImGuiKey_Comma || key == ImGuiKey_Minus || key == ImGuiKey_Period + || key == ImGuiKey_Slash || key == ImGuiKey_Semicolon || key == ImGuiKey_Equal || key == ImGuiKey_LeftBracket || key == ImGuiKey_RightBracket || key == ImGuiKey_GraveAccent + || key == ImGuiKey_KeypadDecimal || key == ImGuiKey_KeypadDivide || key == ImGuiKey_KeypadMultiply || key == ImGuiKey_KeypadSubtract || key == ImGuiKey_KeypadAdd || key == ImGuiKey_KeypadEqual) + g.KeysMayBeCharInput.SetBit(key); + #ifdef IMGUI_HAS_DOCK #endif @@ -3880,6 +3900,7 @@ void ImGui::SetActiveID(ImGuiID id, ImGuiWindow* window) g.ActiveIdNoClearOnFocusLoss = false; g.ActiveIdWindow = window; g.ActiveIdHasBeenEditedThisFrame = false; + g.ActiveIdFromShortcut = false; if (id) { g.ActiveIdIsAlive = id; @@ -3916,17 +3937,6 @@ ImGuiID ImGui::GetHoveredID() return g.HoveredId ? g.HoveredId : g.HoveredIdPreviousFrame; } -// This is called by ItemAdd(). -// Code not using ItemAdd() may need to call this manually otherwise ActiveId will be cleared. In IMGUI_VERSION_NUM < 18717 this was called by GetID(). -void ImGui::KeepAliveID(ImGuiID id) -{ - ImGuiContext& g = *GImGui; - if (g.ActiveId == id) - g.ActiveIdIsAlive = id; - if (g.ActiveIdPreviousFrame == id) - g.ActiveIdPreviousFrameIsAlive = true; -} - void ImGui::MarkItemEdited(ImGuiID id) { // This marking is solely to be able to provide info for IsItemDeactivatedAfterEdit(). @@ -4100,7 +4110,8 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag if (g.HoveredId != 0 && g.HoveredId != id && !g.HoveredIdAllowOverlap) return false; if (g.ActiveId != 0 && g.ActiveId != id && !g.ActiveIdAllowOverlap) - return false; + if (!g.ActiveIdFromShortcut) + return false; // Done with rectangle culling so we can perform heavier checks now. if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) @@ -4139,17 +4150,19 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag return false; } +#ifndef IMGUI_DISABLE_DEBUG_TOOLS if (id != 0) { // [DEBUG] Item Picker tool! - // We perform the check here because SetHoveredID() is not frequently called (1~ time a frame), making - // the cost of this tool near-zero. We can get slightly better call-stack and support picking non-hovered - // items if we performed the test in ItemAdd(), but that would incur a small runtime cost. + // We perform the check here because reaching is path is rare (1~ time a frame), + // making the cost of this tool near-zero! We could get better call-stack and support picking non-hovered + // items if we performed the test in ItemAdd(), but that would incur a bigger runtime cost. if (g.DebugItemPickerActive && g.HoveredIdPreviousFrame == id) GetForegroundDrawList()->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)); if (g.DebugItemPickerBreakId == id) IM_DEBUG_BREAK(); } +#endif if (g.NavDisableMouseHover) return false; @@ -4158,12 +4171,13 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id, ImGuiItemFlags item_flag } // FIXME: This is inlined/duplicated in ItemAdd() +// FIXME: The id != 0 path is not used by our codebase, may get rid of it? bool ImGui::IsClippedEx(const ImRect& bb, ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; if (!bb.Overlaps(window->ClipRect)) - if (id == 0 || (id != g.ActiveId && id != g.NavId)) + if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId)) if (!g.LogEnabled) return true; return false; @@ -4784,6 +4798,7 @@ void ImGui::NewFrame() g.GroupStack.resize(0); // [DEBUG] Update debug features +#ifndef IMGUI_DISABLE_DEBUG_TOOLS UpdateDebugToolItemPicker(); UpdateDebugToolStackQueries(); UpdateDebugToolFlashStyleColor(); @@ -4798,6 +4813,7 @@ void ImGui::NewFrame() g.DebugLogFlags &= ~g.DebugLogAutoDisableFlags; g.DebugLogAutoDisableFlags = ImGuiDebugLogFlags_None; } +#endif // Create implicit/fallback window - which we will only render it if the user has added something to it. // We don't use "Debug" to avoid colliding with user trying to create a "Debug" window with custom flags. @@ -4809,10 +4825,12 @@ void ImGui::NewFrame() // [DEBUG] When io.ConfigDebugBeginReturnValue is set, we make Begin()/BeginChild() return false at different level of the window-stack, // allowing to validate correct Begin/End behavior in user code. +#ifndef IMGUI_DISABLE_DEBUG_TOOLS if (g.IO.ConfigDebugBeginReturnValueLoop) g.DebugBeginReturnValueCullDepth = (g.DebugBeginReturnValueCullDepth == -1) ? 0 : ((g.DebugBeginReturnValueCullDepth + ((g.FrameCount % 4) == 0 ? 1 : 0)) % 10); else g.DebugBeginReturnValueCullDepth = -1; +#endif CallContextHooks(&g, ImGuiContextHookType_NewFramePost); } @@ -5029,18 +5047,7 @@ void ImGui::EndFrame() { IMGUI_DEBUG_LOG_IO("[io] Calling io.SetPlatformImeDataFn(): WantVisible: %d, InputPos (%.2f,%.2f)\n", ime_data->WantVisible, ime_data->InputPos.x, ime_data->InputPos.y); ImGuiViewport* viewport = GetMainViewport(); -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (viewport->PlatformHandleRaw == NULL && g.IO.ImeWindowHandle != NULL) - { - viewport->PlatformHandleRaw = g.IO.ImeWindowHandle; - g.IO.SetPlatformImeDataFn(viewport, ime_data); - viewport->PlatformHandleRaw = NULL; - } - else -#endif - { - g.IO.SetPlatformImeDataFn(viewport, ime_data); - } + g.IO.SetPlatformImeDataFn(viewport, ime_data); } // Hide implicit/fallback "Debug" window if it hasn't been used @@ -5403,7 +5410,7 @@ ImVec2 ImGui::GetItemRectSize() } // Prior to v1.90 2023/10/16, the BeginChild() function took a 'bool border = false' parameter instead of 'ImGuiChildFlags child_flags = 0'. -// ImGuiChildFlags_Border is defined as always == 1 in order to allow old code passing 'true'. +// ImGuiChildFlags_Border is defined as always == 1 in order to allow old code passing 'true'. Read comments in imgui.h for details! bool ImGui::BeginChild(const char* str_id, const ImVec2& size_arg, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags) { ImGuiID id = GetCurrentWindow()->GetID(str_id); @@ -5545,7 +5552,7 @@ void ImGui::EndChild() // When browsing a window that has no activable items (scroll only) we keep a highlight on the child (pass g.NavId to trick into always displaying) if (child_window->DC.NavLayersActiveMask == 0 && child_window == g.NavWindow) - RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_TypeThin); + RenderNavHighlight(ImRect(bb.Min - ImVec2(2, 2), bb.Max + ImVec2(2, 2)), g.NavId, ImGuiNavHighlightFlags_Compact); } else { @@ -5755,7 +5762,7 @@ static ImVec2 CalcWindowAutoFitSize(ImGuiWindow* window, const ImVec2& size_cont { // Maximum window size is determined by the viewport size or monitor size ImVec2 size_min = CalcWindowMinSize(window); - ImVec2 size_max = (window->Flags & ImGuiWindowFlags_ChildWindow) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; + ImVec2 size_max = ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) ? ImVec2(FLT_MAX, FLT_MAX) : ImGui::GetMainViewport()->WorkSize - style.DisplaySafeAreaPadding * 2.0f; ImVec2 size_auto_fit = ImClamp(size_desired, size_min, size_max); // When the window cannot fit all contents (either because of constraints, either because screen is too small), @@ -6436,21 +6443,22 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window_stack_data.StackSizesOnBegin.SetToContextState(&g); g.CurrentWindowStack.push_back(window_stack_data); if (flags & ImGuiWindowFlags_ChildMenu) - g.BeginMenuCount++; + g.BeginMenuDepth++; // Update ->RootWindow and others pointers (before any possible call to FocusWindow) if (first_begin_of_the_frame) { UpdateWindowParentAndRootLinks(window, flags, parent_window); window->ParentWindowInBeginStack = parent_window_in_stack; + + // There's little point to expose a flag to set this: because the interesting cases won't be using parent_window_in_stack, + // e.g. linking a tool window in a standalone viewport to a document window, regardless of their Begin() stack parenting. (#6798) + window->ParentWindowForFocusRoute = (flags & ImGuiWindowFlags_ChildWindow) ? parent_window_in_stack : NULL; } // Add to focus scope stack - // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() - if ((flags & ImGuiWindowFlags_NavFlattened) == 0) - PushFocusScope(window->ID); + PushFocusScope((flags & ImGuiWindowFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID); window->NavRootFocusScopeId = g.CurrentFocusScopeId; - g.CurrentWindow = NULL; // Add to popup stack if (flags & ImGuiWindowFlags_Popup) @@ -6516,6 +6524,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->Appearing) SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() + g.CurrentWindow = NULL; + // When reusing window again multiple times a frame, just append content (don't need to setup again) if (first_begin_of_the_frame) { @@ -7058,12 +7069,15 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // [DEBUG] io.ConfigDebugBeginReturnValue override return value to test Begin/End and BeginChild/EndChild behaviors. // (The implicit fallback window is NOT automatically ended allowing it to always be able to receive commands without crashing) - if (!window->IsFallbackWindow && ((g.IO.ConfigDebugBeginReturnValueOnce && window_just_created) || (g.IO.ConfigDebugBeginReturnValueLoop && g.DebugBeginReturnValueCullDepth == g.CurrentWindowStack.Size))) - { - if (window->AutoFitFramesX > 0) { window->AutoFitFramesX++; } - if (window->AutoFitFramesY > 0) { window->AutoFitFramesY++; } - return false; - } +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (!window->IsFallbackWindow) + if ((g.IO.ConfigDebugBeginReturnValueOnce && window_just_created) || (g.IO.ConfigDebugBeginReturnValueLoop && g.DebugBeginReturnValueCullDepth == g.CurrentWindowStack.Size)) + { + if (window->AutoFitFramesX > 0) { window->AutoFitFramesX++; } + if (window->AutoFitFramesY > 0) { window->AutoFitFramesY++; } + return false; + } +#endif return !window->SkipItems; } @@ -7089,8 +7103,7 @@ void ImGui::End() if (window->DC.CurrentColumns) EndColumns(); PopClipRect(); // Inner window clip rectangle - if ((window->Flags & ImGuiWindowFlags_NavFlattened) == 0) - PopFocusScope(); + PopFocusScope(); // Stop logging if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging @@ -7102,7 +7115,7 @@ void ImGui::End() // Pop from window stack g.LastItemData = g.CurrentWindowStack.back().ParentLastItemDataBackup; if (window->Flags & ImGuiWindowFlags_ChildMenu) - g.BeginMenuCount--; + g.BeginMenuDepth--; if (window->Flags & ImGuiWindowFlags_Popup) g.BeginPopupStack.pop_back(); g.CurrentWindowStack.back().StackSizesOnBegin.CompareWithContextState(&g); @@ -7215,7 +7228,7 @@ void ImGui::FocusWindow(ImGuiWindow* window, ImGuiFocusRequestFlags flags) g.NavMousePosDirty = true; g.NavId = window ? window->NavLastIds[0] : 0; // Restore NavId g.NavLayer = ImGuiNavLayer_Main; - g.NavFocusScopeId = window ? window->NavRootFocusScopeId : 0; + SetNavFocusScope(window ? window->NavRootFocusScopeId : 0); g.NavIdIsAlive = false; g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; @@ -7809,16 +7822,50 @@ void ImGui::SetWindowFontScale(float scale) void ImGui::PushFocusScope(ImGuiID id) { ImGuiContext& g = *GImGui; - g.FocusScopeStack.push_back(id); + ImGuiFocusScopeData data; + data.ID = id; + data.WindowID = g.CurrentWindow->ID; + g.FocusScopeStack.push_back(data); g.CurrentFocusScopeId = id; } void ImGui::PopFocusScope() { ImGuiContext& g = *GImGui; - IM_ASSERT(g.FocusScopeStack.Size > 0); // Too many PopFocusScope() ? + if (g.FocusScopeStack.Size == 0) + { + IM_ASSERT_USER_ERROR(g.FocusScopeStack.Size > 0, "Calling PopFocusScope() too many times!"); + return; + } g.FocusScopeStack.pop_back(); - g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back() : 0; + g.CurrentFocusScopeId = g.FocusScopeStack.Size ? g.FocusScopeStack.back().ID : 0; +} + +void ImGui::SetNavFocusScope(ImGuiID focus_scope_id) +{ + ImGuiContext& g = *GImGui; + g.NavFocusScopeId = focus_scope_id; + g.NavFocusRoute.resize(0); // Invalidate + if (focus_scope_id == 0) + return; + IM_ASSERT(g.NavWindow != NULL); + + // Store current path (in reverse order) + if (focus_scope_id == g.CurrentFocusScopeId) + { + // Top of focus stack contains local focus scopes inside current window + for (int n = g.FocusScopeStack.Size - 1; n >= 0 && g.FocusScopeStack.Data[n].WindowID == g.CurrentWindow->ID; n--) + g.NavFocusRoute.push_back(g.FocusScopeStack.Data[n]); + } + else if (focus_scope_id == g.NavWindow->NavRootFocusScopeId) + g.NavFocusRoute.push_back({ focus_scope_id, g.NavWindow->ID }); + else + return; + + // Then follow on manually set ParentWindowForFocusRoute field (#6798) + for (ImGuiWindow* window = g.NavWindow->ParentWindowForFocusRoute; window != NULL; window = window->ParentWindowForFocusRoute) + g.NavFocusRoute.push_back({ window->NavRootFocusScopeId, window->ID }); + IM_ASSERT(g.NavFocusRoute.Size < 100); // Maximum depth is technically 251 as per CalcRoutingScore(): 254 - 3 } // Focus = move navigation cursor, set scrolling, set focus window. @@ -8077,6 +8124,26 @@ bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) // - Shortcut() [Internal] //----------------------------------------------------------------------------- +ImGuiKeyChord ImGui::FixupKeyChord(ImGuiContext* ctx, ImGuiKeyChord key_chord) +{ + // Convert ImGuiMod_Shortcut and add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + if (IsModKey(key)) + { + if (key == ImGuiKey_LeftCtrl || key == ImGuiKey_RightCtrl) + key_chord |= ImGuiMod_Ctrl; + if (key == ImGuiKey_LeftShift || key == ImGuiKey_RightShift) + key_chord |= ImGuiMod_Shift; + if (key == ImGuiKey_LeftAlt || key == ImGuiKey_RightAlt) + key_chord |= ImGuiMod_Alt; + if (key == ImGuiKey_LeftSuper || key == ImGuiKey_RightSuper) + key_chord |= ImGuiMod_Super; + } + if (key_chord & ImGuiMod_Shortcut) + return (key_chord & ~ImGuiMod_Shortcut) | (ctx->IO.ConfigMacOSXBehaviors ? ImGuiMod_Super : ImGuiMod_Ctrl); + return key_chord; +} + ImGuiKeyData* ImGui::GetKeyData(ImGuiContext* ctx, ImGuiKey key) { ImGuiContext& g = *ctx; @@ -8157,18 +8224,17 @@ const char* ImGui::GetKeyName(ImGuiKey key) } // ImGuiMod_Shortcut is translated to either Ctrl or Super. -const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord, char* out_buf, int out_buf_size) +const char* ImGui::GetKeyChordName(ImGuiKeyChord key_chord) { ImGuiContext& g = *GImGui; - if (key_chord & ImGuiMod_Shortcut) - key_chord = ConvertShortcutMod(key_chord); - ImFormatString(out_buf, (size_t)out_buf_size, "%s%s%s%s%s", + key_chord = FixupKeyChord(&g, key_chord); + ImFormatString(g.TempKeychordName, IM_ARRAYSIZE(g.TempKeychordName), "%s%s%s%s%s", (key_chord & ImGuiMod_Ctrl) ? "Ctrl+" : "", (key_chord & ImGuiMod_Shift) ? "Shift+" : "", (key_chord & ImGuiMod_Alt) ? "Alt+" : "", (key_chord & ImGuiMod_Super) ? (g.IO.ConfigMacOSXBehaviors ? "Cmd+" : "Super+") : "", GetKeyName((ImGuiKey)(key_chord & ~ImGuiMod_Mask_))); - return out_buf; + return g.TempKeychordName; } // t0 = previous time (e.g.: g.Time - g.IO.DeltaTime) @@ -8235,6 +8301,7 @@ static void ImGui::UpdateKeyRoutingTable(ImGuiKeyRoutingTable* rt) for (int old_routing_idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; old_routing_idx != -1; old_routing_idx = routing_entry->NextEntryIndex) { routing_entry = &rt->Entries[old_routing_idx]; + routing_entry->RoutingCurrScore = routing_entry->RoutingNextScore; routing_entry->RoutingCurr = routing_entry->RoutingNext; // Update entry routing_entry->RoutingNext = ImGuiKeyOwner_None; routing_entry->RoutingNextScore = 255; @@ -8281,13 +8348,11 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) ImGuiContext& g = *GImGui; ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable; ImGuiKeyRoutingData* routing_data; - if (key_chord & ImGuiMod_Shortcut) - key_chord = ConvertShortcutMod(key_chord); ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); if (key == ImGuiKey_None) key = ConvertSingleModFlagToKey(&g, mods); - IM_ASSERT(IsNamedKey(key)); + IM_ASSERT(IsNamedKey(key) && (key_chord & ImGuiMod_Shortcut) == 0); // Please call ConvertShortcutMod() in calling function. // Get (in the majority of case, the linked list will have one element so this should be 2 reads. // Subsequent elements will be contiguous in memory as list is sorted/rebuilt in NewFrame). @@ -8316,37 +8381,30 @@ ImGuiKeyRoutingData* ImGui::GetShortcutRoutingData(ImGuiKeyChord key_chord) // - 254: ImGuiInputFlags_RouteGlobalLow // - 255: never route // 'flags' should include an explicit routing policy -static int CalcRoutingScore(ImGuiWindow* location, ImGuiID owner_id, ImGuiInputFlags flags) +static int CalcRoutingScore(ImGuiID focus_scope_id, ImGuiID owner_id, ImGuiInputFlags flags) { if (flags & ImGuiInputFlags_RouteFocused) { ImGuiContext& g = *GImGui; - ImGuiWindow* focused = g.NavWindow; // ActiveID gets top priority // (we don't check g.ActiveIdUsingAllKeys here. Routing is applied but if input ownership is tested later it may discard it) if (owner_id != 0 && g.ActiveId == owner_id) return 1; - // Early out when not in focus stack - if (focused == NULL || focused->RootWindow != location->RootWindow) - return 255; - // Score based on distance to focused window (lower is better) // Assuming both windows are submitting a routing request, // - When Window....... is focused -> Window scores 3 (best), Window/ChildB scores 255 (no match) // - When Window/ChildB is focused -> Window scores 4, Window/ChildB scores 3 (best) // Assuming only WindowA is submitting a routing request, // - When Window/ChildB is focused -> Window scores 4 (best), Window/ChildB doesn't have a score. - for (int next_score = 3; focused != NULL; next_score++) - { - if (focused == location) - { - IM_ASSERT(next_score < 255); - return next_score; - } - focused = (focused->RootWindow != focused) ? focused->ParentWindow : NULL; // FIXME: This could be later abstracted as a focus path - } + // This essentially follow the window->ParentWindowForFocusRoute chain. + if (focus_scope_id == 0) + return 255; + for (int index_in_focus_path = 0; index_in_focus_path < g.NavFocusRoute.Size; index_in_focus_path++) + if (g.NavFocusRoute.Data[index_in_focus_path].ID == focus_scope_id) + return 3 + index_in_focus_path; + return 255; } @@ -8358,13 +8416,29 @@ static int CalcRoutingScore(ImGuiWindow* location, ImGuiID owner_id, ImGuiInputF return 0; } +// We need this to filter some Shortcut() routes when an item e.g. an InputText() is active +// e.g. ImGuiKey_G won't be considered a shortcut when item is active, but ImGuiMod|ImGuiKey_G can be. +static bool IsKeyChordPotentiallyCharInput(ImGuiKeyChord key_chord) +{ + // Mimic 'ignore_char_inputs' logic in InputText() + ImGuiContext& g = *GImGui; + + // When the right mods are pressed it cannot be a char input so we won't filter the shortcut out. + ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); + const bool ignore_char_inputs = ((mods & ImGuiMod_Ctrl) && !(mods & ImGuiMod_Alt)) || (g.IO.ConfigMacOSXBehaviors && (mods & ImGuiMod_Super)); + if (ignore_char_inputs) + return false; + + // Return true for A-Z, 0-9 and other keys associated to char inputs. Other keys such as F1-F12 won't be filtered. + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + return g.KeysMayBeCharInput.TestBit(key); +} + // Request a desired route for an input chord (key + mods). // Return true if the route is available this frame. // - Routes and key ownership are attributed at the beginning of next frame based on best score and mod state. // (Conceptually this does a "Submit for next frame" + "Test for current frame". // As such, it could be called TrySetXXX or SubmitXXX, or the Submit and Test operations should be separate.) -// - Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) -// - Using 'owner_id == ImGuiKeyOwner_None': allows disabling/locking a shortcut. bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; @@ -8372,6 +8446,10 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI flags |= ImGuiInputFlags_RouteGlobalHigh; // IMPORTANT: This is the default for SetShortcutRouting() but NOT Shortcut() else IM_ASSERT(ImIsPowerOfTwo(flags & ImGuiInputFlags_RouteMask_)); // Check that only 1 routing flag is used + IM_ASSERT(owner_id != ImGuiKeyOwner_Any && owner_id != ImGuiKeyOwner_None); + + // Convert ImGuiMod_Shortcut and add ImGuiMod_XXXX when a corresponding ImGuiKey_LeftXXX/ImGuiKey_RightXXX is specified. + key_chord = FixupKeyChord(&g, key_chord); // [DEBUG] Debug break requested by user if (g.DebugBreakInShortcutRouting == key_chord) @@ -8380,34 +8458,68 @@ bool ImGui::SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiI if (flags & ImGuiInputFlags_RouteUnlessBgFocused) if (g.NavWindow == NULL) return false; + // Note how ImGuiInputFlags_RouteAlways won't set routing and thus won't set owner. May want to rework this? if (flags & ImGuiInputFlags_RouteAlways) + { + IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> always\n", GetKeyChordName(key_chord), owner_id, flags); return true; + } - const int score = CalcRoutingScore(g.CurrentWindow, owner_id, flags); + // Specific culling when there's an active. + if (g.ActiveId != 0 && g.ActiveId != owner_id) + { + // Cull shortcuts with no modifiers when it could generate a character. + // e.g. Shortcut(ImGuiKey_G) also generates 'g' character, should not trigger when InputText() is active. + // but Shortcut(Ctrl+G) should generally trigger when InputText() is active. + // TL;DR: lettered shortcut with no mods or with only Alt mod will not trigger while an item reading text input is active. + // (We cannot filter based on io.InputQueueCharacters[] contents because of trickling and key<>chars submission order are undefined) + if ((flags & ImGuiInputFlags_RouteFocused) && g.IO.WantTextInput && IsKeyChordPotentiallyCharInput(key_chord)) + { + IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> filtered as potential char input\n", GetKeyChordName(key_chord), owner_id, flags); + return false; + } + + // ActiveIdUsingAllKeyboardKeys trumps all for ActiveId + if ((flags & ImGuiInputFlags_RouteGlobalHigh) == 0 && g.ActiveIdUsingAllKeyboardKeys) + { + ImGuiKey key = (ImGuiKey)(key_chord & ~ImGuiMod_Mask_); + if (key == ImGuiKey_None) + key = ConvertSingleModFlagToKey(&g, (ImGuiKey)(key_chord & ImGuiMod_Mask_)); + if (key >= ImGuiKey_Keyboard_BEGIN && key < ImGuiKey_Keyboard_END) + return false; + } + } + + // FIXME-SHORTCUT: A way to configure the location/focus-scope to test would render this more flexible. + const int score = CalcRoutingScore(g.CurrentFocusScopeId, owner_id, flags); + IMGUI_DEBUG_LOG_INPUTROUTING("SetShortcutRouting(%s, owner_id=0x%08X, flags=%04X) -> score %d\n", GetKeyChordName(key_chord), owner_id, flags, score); if (score == 255) return false; // Submit routing for NEXT frame (assuming score is sufficient) // FIXME: Could expose a way to use a "serve last" policy for same score resolution (using <= instead of <). ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); - const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); //const bool set_route = (flags & ImGuiInputFlags_ServeLast) ? (score <= routing_data->RoutingNextScore) : (score < routing_data->RoutingNextScore); if (score < routing_data->RoutingNextScore) { - routing_data->RoutingNext = routing_id; + routing_data->RoutingNext = owner_id; routing_data->RoutingNextScore = (ImU8)score; } // Return routing state for CURRENT frame - return routing_data->RoutingCurr == routing_id; + if (routing_data->RoutingCurr == owner_id) + IMGUI_DEBUG_LOG_INPUTROUTING("--> granting current route\n"); + return routing_data->RoutingCurr == owner_id; } // Currently unused by core (but used by tests) // Note: this cannot be turned into GetShortcutRouting() because we do the owner_id->routing_id translation, name would be more misleading. bool ImGui::TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id) { + ImGuiContext& g = *GImGui; const ImGuiID routing_id = GetRoutingIdFromOwnerId(owner_id); + key_chord = FixupKeyChord(&g, key_chord); ImGuiKeyRoutingData* routing_data = GetShortcutRoutingData(key_chord); // FIXME: Could avoid creating entry. return routing_data->RoutingCurr == routing_id; } @@ -9338,8 +9450,7 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord) bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) { ImGuiContext& g = *GImGui; - if (key_chord & ImGuiMod_Shortcut) - key_chord = ConvertShortcutMod(key_chord); + key_chord = FixupKeyChord(&g, key_chord); ImGuiKey mods = (ImGuiKey)(key_chord & ImGuiMod_Mask_); if (g.IO.KeyMods != mods) return false; @@ -9353,6 +9464,13 @@ bool ImGui::IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiIn return true; } +void ImGui::SetNextItemShortcut(ImGuiKeyChord key_chord) +{ + ImGuiContext& g = *GImGui; + g.NextItemData.Flags |= ImGuiNextItemDataFlags_HasShortcut; + g.NextItemData.Shortcut = key_chord; +} + bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags) { //ImGuiContext& g = *GImGui; @@ -9361,12 +9479,19 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags // When using (owner_id == 0/Any): SetShortcutRouting() will use CurrentFocusScopeId and filter with this, so IsKeyPressed() is fine with he 0/Any. if ((flags & ImGuiInputFlags_RouteMask_) == 0) flags |= ImGuiInputFlags_RouteFocused; + + // Using 'owner_id == ImGuiKeyOwner_Any/0': auto-assign an owner based on current focus scope (each window has its focus scope by default) + // Effectively makes Shortcut() always input-owner aware. + if (owner_id == ImGuiKeyOwner_Any || owner_id == ImGuiKeyOwner_None) + owner_id = GetRoutingIdFromOwnerId(owner_id); + + // Submit route if (!SetShortcutRouting(key_chord, owner_id, flags)) return false; // Default repeat behavior for Shortcut() // So e.g. pressing Ctrl+W and releasing Ctrl while holding W will not trigger the W shortcut. - if ((flags & ImGuiInputFlags_RepeatUntilMask_) == 0) + if ((flags & ImGuiInputFlags_Repeat) != 0 && (flags & ImGuiInputFlags_RepeatUntilMask_) == 0) flags |= ImGuiInputFlags_RepeatUntilKeyModsChange; if (!IsKeyChordPressed(key_chord, owner_id, flags)) @@ -9652,12 +9777,135 @@ void ImGuiStackSizes::CompareWithContextState(ImGuiContext* ctx) IM_ASSERT(SizeOfFocusScopeStack == g.FocusScopeStack.Size && "PushFocusScope/PopFocusScope Mismatch!"); } +//----------------------------------------------------------------------------- +// [SECTION] ITEM SUBMISSION +//----------------------------------------------------------------------------- +// - KeepAliveID() +// - ItemHandleShortcut() [Internal] +// - ItemAdd() +//----------------------------------------------------------------------------- + +// Code not using ItemAdd() may need to call this manually otherwise ActiveId will be cleared. In IMGUI_VERSION_NUM < 18717 this was called by GetID(). +void ImGui::KeepAliveID(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + if (g.ActiveId == id) + g.ActiveIdIsAlive = id; + if (g.ActiveIdPreviousFrame == id) + g.ActiveIdPreviousFrameIsAlive = true; +} + +static void ItemHandleShortcut(ImGuiID id) +{ + // FIXME: Generalize Activation queue? + ImGuiContext& g = *GImGui; + if (ImGui::Shortcut(g.NextItemData.Shortcut, id, ImGuiInputFlags_None) && g.NavActivateId == 0) + { + g.NavActivateId = id; // Will effectively disable clipping. + g.NavActivateFlags = ImGuiActivateFlags_PreferInput | ImGuiActivateFlags_FromShortcut; + if (g.ActiveId == 0 || g.ActiveId == id) + g.NavActivateDownId = g.NavActivatePressedId = id; + ImGui::NavHighlightActivated(id); + } +} + +// Declare item bounding box for clipping and interaction. +// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface +// declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. +// THIS IS IN THE PERFORMANCE CRITICAL PATH (UNTIL THE CLIPPING TEST AND EARLY-RETURN) +bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags) +{ + ImGuiContext& g = *GImGui; + ImGuiWindow* window = g.CurrentWindow; + + // Set item data + // (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags_HasDisplayRect is set) + g.LastItemData.ID = id; + g.LastItemData.Rect = bb; + g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb; + g.LastItemData.InFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; + g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; + // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared. + + if (id != 0) + { + KeepAliveID(id); + + // Directional navigation processing + // Runs prior to clipping early-out + // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget + // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests + // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of + // thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. + // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able + // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). + // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null. + // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere. + if (!(g.LastItemData.InFlags & ImGuiItemFlags_NoNav)) + { + // FIMXE-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId == g.CurrentFocusScopeId)' test. + window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); + if (g.NavId == id || g.NavAnyRequest) + if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) + if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) + NavProcessItem(); + } + + if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasShortcut) + ItemHandleShortcut(id); + } + + // Lightweight clear of SetNextItemXXX data. + g.NextItemData.Flags = ImGuiNextItemDataFlags_None; + g.NextItemData.ItemFlags = ImGuiItemFlags_None; + +#ifdef IMGUI_ENABLE_TEST_ENGINE + if (id != 0) + IMGUI_TEST_ENGINE_ITEM_ADD(id, g.LastItemData.NavRect, &g.LastItemData); +#endif + + // Clipping test + // (this is a modified copy of IsClippedEx() so we can reuse the is_rect_visible value) + //const bool is_clipped = IsClippedEx(bb, id); + //if (is_clipped) + // return false; + // g.NavActivateId is not necessarily == g.NavId, in the case of remote activation (e.g. shortcuts) + const bool is_rect_visible = bb.Overlaps(window->ClipRect); + if (!is_rect_visible) + if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId && id != g.NavActivateId)) + if (!g.LogEnabled) + return false; + + // [DEBUG] +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + if (id != 0) + { + if (id == g.DebugLocateId) + DebugLocateItemResolveWithLastItem(); + + // [DEBUG] People keep stumbling on this problem and using "" as identifier in the root of a window instead of "##something". + // Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use "##something". + // READ THE FAQ: https://dearimgui.com/faq + IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!"); + } + //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] + //if ((g.LastItemData.InFlags & ImGuiItemFlags_NoNav) == 0) + // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] +#endif + + // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) + if (is_rect_visible) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible; + if (IsMouseHoveringRect(bb.Min, bb.Max)) + g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; + return true; +} + //----------------------------------------------------------------------------- // [SECTION] LAYOUT //----------------------------------------------------------------------------- // - ItemSize() -// - ItemAdd() // - SameLine() // - GetCursorScreenPos() // - SetCursorScreenPos() @@ -9725,94 +9973,6 @@ void ImGui::ItemSize(const ImVec2& size, float text_baseline_y) SameLine(); } -// Declare item bounding box for clipping and interaction. -// Note that the size can be different than the one provided to ItemSize(). Typically, widgets that spread over available surface -// declare their minimum size requirement to ItemSize() and provide a larger region to ItemAdd() which is used drawing/interaction. -// THIS IS IN THE PERFORMANCE CRITICAL PATH (UNTIL THE CLIPPING TEST AND EARLY-RETURN) -bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id, const ImRect* nav_bb_arg, ImGuiItemFlags extra_flags) -{ - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - - // Set item data - // (DisplayRect is left untouched, made valid when ImGuiItemStatusFlags_HasDisplayRect is set) - g.LastItemData.ID = id; - g.LastItemData.Rect = bb; - g.LastItemData.NavRect = nav_bb_arg ? *nav_bb_arg : bb; - g.LastItemData.InFlags = g.CurrentItemFlags | g.NextItemData.ItemFlags | extra_flags; - g.LastItemData.StatusFlags = ImGuiItemStatusFlags_None; - // Note: we don't copy 'g.NextItemData.SelectionUserData' to an hypothetical g.LastItemData.SelectionUserData: since the former is not cleared. - - if (id != 0) - { - KeepAliveID(id); - - // Directional navigation processing - // Runs prior to clipping early-out - // (a) So that NavInitRequest can be honored, for newly opened windows to select a default widget - // (b) So that we can scroll up/down past clipped items. This adds a small O(N) cost to regular navigation requests - // unfortunately, but it is still limited to one window. It may not scale very well for windows with ten of - // thousands of item, but at least NavMoveRequest is only set on user interaction, aka maximum once a frame. - // We could early out with "if (is_clipped && !g.NavInitRequest) return false;" but when we wouldn't be able - // to reach unclipped widgets. This would work if user had explicit scrolling control (e.g. mapped on a stick). - // We intentionally don't check if g.NavWindow != NULL because g.NavAnyRequest should only be set when it is non null. - // If we crash on a NULL g.NavWindow we need to fix the bug elsewhere. - if (!(g.LastItemData.InFlags & ImGuiItemFlags_NoNav)) - { - // FIMXE-NAV: investigate changing the window tests into a simple 'if (g.NavFocusScopeId == g.CurrentFocusScopeId)' test. - window->DC.NavLayersActiveMaskNext |= (1 << window->DC.NavLayerCurrent); - if (g.NavId == id || g.NavAnyRequest) - if (g.NavWindow->RootWindowForNav == window->RootWindowForNav) - if (window == g.NavWindow || ((window->Flags | g.NavWindow->Flags) & ImGuiWindowFlags_NavFlattened)) - NavProcessItem(); - } - } - - // Lightweight clear of SetNextItemXXX data. - g.NextItemData.Flags = ImGuiNextItemDataFlags_None; - g.NextItemData.ItemFlags = ImGuiItemFlags_None; - -#ifdef IMGUI_ENABLE_TEST_ENGINE - if (id != 0) - IMGUI_TEST_ENGINE_ITEM_ADD(id, g.LastItemData.NavRect, &g.LastItemData); -#endif - - // Clipping test - // (this is a modified copy of IsClippedEx() so we can reuse the is_rect_visible value) - //const bool is_clipped = IsClippedEx(bb, id); - //if (is_clipped) - // return false; - const bool is_rect_visible = bb.Overlaps(window->ClipRect); - if (!is_rect_visible) - if (id == 0 || (id != g.ActiveId && id != g.ActiveIdPreviousFrame && id != g.NavId)) - if (!g.LogEnabled) - return false; - - // [DEBUG] -#ifndef IMGUI_DISABLE_DEBUG_TOOLS - if (id != 0) - { - if (id == g.DebugLocateId) - DebugLocateItemResolveWithLastItem(); - - // [DEBUG] People keep stumbling on this problem and using "" as identifier in the root of a window instead of "##something". - // Empty identifier are valid and useful in a small amount of cases, but 99.9% of the time you want to use "##something". - // READ THE FAQ: https://dearimgui.com/faq - IM_ASSERT(id != window->ID && "Cannot have an empty ID at the root of a window. If you need an empty label, use ## and read the FAQ about how the ID Stack works!"); - } - //if (g.IO.KeyAlt) window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255,255,0,120)); // [DEBUG] - //if ((g.LastItemData.InFlags & ImGuiItemFlags_NoNav) == 0) - // window->DrawList->AddRect(g.LastItemData.NavRect.Min, g.LastItemData.NavRect.Max, IM_COL32(255,255,0,255)); // [DEBUG] -#endif - - // We need to calculate this now to take account of the current clipping rectangle (as items like Selectable may change them) - if (is_rect_visible) - g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_Visible; - if (IsMouseHoveringRect(bb.Min, bb.Max)) - g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredRect; - return true; -} - // Gets back to previous line and continue with horizontal layout // offset_from_start_x == 0 : follow right after previous item // offset_from_start_x != 0 : align to specified x position (relative to window/group left) @@ -10622,16 +10782,22 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) } else { - // Gently handle the user mistakenly calling OpenPopup() every frame. It is a programming mistake! However, if we were to run the regular code path, the ui - // would become completely unusable because the popup will always be in hidden-while-calculating-size state _while_ claiming focus. Which would be a very confusing - // situation for the programmer. Instead, we silently allow the popup to proceed, it will keep reappearing and the programming error will be more obvious to understand. - if (g.OpenPopupStack[current_stack_size].PopupId == id && g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) + // Gently handle the user mistakenly calling OpenPopup() every frames: it is likely a programming mistake! + // However, if we were to run the regular code path, the ui would become completely unusable because the popup will always be + // in hidden-while-calculating-size state _while_ claiming focus. Which is extremely confusing situation for the programmer. + // Instead, for successive frames calls to OpenPopup(), we silently avoid reopening even if ImGuiPopupFlags_NoReopen is not specified. + bool keep_existing = false; + if (g.OpenPopupStack[current_stack_size].PopupId == id) + if ((g.OpenPopupStack[current_stack_size].OpenFrameCount == g.FrameCount - 1) || (popup_flags & ImGuiPopupFlags_NoReopen)) + keep_existing = true; + if (keep_existing) { + // No reopen g.OpenPopupStack[current_stack_size].OpenFrameCount = popup_ref.OpenFrameCount; } else { - // Close child popups if any, then flag popup for open/reopen + // Reopen: close child popups if any, then flag popup for open/reopen (set position, focus, init navigation) ClosePopupToLevel(current_stack_size, false); g.OpenPopupStack.push_back(popup_ref); } @@ -10662,14 +10828,15 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to if (!popup.Window) continue; IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0); - if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) - continue; // Trim the stack unless the popup is a direct parent of the reference window (the reference window is often the NavWindow) - // - With this stack of window, clicking/focusing Popup1 will close Popup2 and Popup3: - // Window -> Popup1 -> Popup2 -> Popup3 + // - Clicking/Focusing Window2 won't close Popup1: + // Window -> Popup1 -> Window2(Ref) + // - Clicking/focusing Popup1 will close Popup2 and Popup3: + // Window -> Popup1(Ref) -> Popup2 -> Popup3 // - Each popups may contain child windows, which is why we compare ->RootWindow! // Window -> Popup1 -> Popup1_Child -> Popup2 -> Popup2_Child + // We step through every popup from bottom to top to validate their position relative to reference window. bool ref_window_is_descendent_of_popup = false; for (int n = popup_count_to_keep; n < g.OpenPopupStack.Size; n++) if (ImGuiWindow* popup_window = g.OpenPopupStack[n].Window) @@ -10768,7 +10935,7 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags) char name[20]; if (flags & ImGuiWindowFlags_ChildMenu) - ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginMenuCount); // Recycle windows based on depth + ImFormatString(name, IM_ARRAYSIZE(name), "##Menu_%02d", g.BeginMenuDepth); // Recycle windows based on depth else ImFormatString(name, IM_ARRAYSIZE(name), "##Popup_%08x", id); // Not recycling, so we can close/open during the same frame @@ -10777,6 +10944,8 @@ bool ImGui::BeginPopupEx(ImGuiID id, ImGuiWindowFlags flags) if (!is_open) // NB: Begin can return false when the popup is completely clipped (e.g. zero size display) EndPopup(); + //g.CurrentWindow->FocusRouteParentWindow = g.CurrentWindow->ParentWindowInBeginStack; + return is_open; } @@ -11078,6 +11247,13 @@ void ImGui::SetNavWindow(ImGuiWindow* window) NavUpdateAnyRequestFlag(); } +void ImGui::NavHighlightActivated(ImGuiID id) +{ + ImGuiContext& g = *GImGui; + g.NavHighlightActivatedId = id; + g.NavHighlightActivatedTimer = NAV_ACTIVATE_HIGHLIGHT_TIMER; +} + void ImGui::NavClearPreferredPosForAxis(ImGuiAxis axis) { ImGuiContext& g = *GImGui; @@ -11091,7 +11267,7 @@ void ImGui::SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id IM_ASSERT(nav_layer == ImGuiNavLayer_Main || nav_layer == ImGuiNavLayer_Menu); g.NavId = id; g.NavLayer = nav_layer; - g.NavFocusScopeId = focus_scope_id; + SetNavFocusScope(focus_scope_id); g.NavWindow->NavLastIds[nav_layer] = id; g.NavWindow->NavRectRel[nav_layer] = rect_rel; @@ -11113,7 +11289,7 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent; g.NavId = id; g.NavLayer = nav_layer; - g.NavFocusScopeId = g.CurrentFocusScopeId; + SetNavFocusScope(g.CurrentFocusScopeId); window->NavLastIds[nav_layer] = id; if (g.LastItemData.ID == id) window->NavRectRel[nav_layer] = WindowRectAbsToRel(window, g.LastItemData.NavRect); @@ -11343,7 +11519,7 @@ static void ImGui::NavProcessItem() // Process Move Request (scoring for navigation) // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRect + scoring from a rect wrapped according to current wrapping policy) - if (g.NavMoveScoringItems && (item_flags & ImGuiItemFlags_Disabled) == 0) + if (g.NavMoveScoringItems && (item_flags & ImGuiItemFlags_Disabled) == 0 && (window->Flags & ImGuiWindowFlags_NoNavInputs) == 0) { const bool is_tabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; if (is_tabbing) @@ -11371,6 +11547,7 @@ static void ImGui::NavProcessItem() if (g.NavWindow != window) SetNavWindow(window); // Always refresh g.NavWindow, because some operations such as FocusItem() may not have a window. g.NavLayer = window->DC.NavLayerCurrent; + SetNavFocusScope(g.CurrentFocusScopeId); // Will set g.NavFocusScopeId AND store g.NavFocusScopePath g.NavFocusScopeId = g.CurrentFocusScopeId; g.NavIdIsAlive = true; if (g.LastItemData.InFlags & ImGuiItemFlags_HasSelectionUserData) @@ -11394,10 +11571,12 @@ void ImGui::NavProcessItemForTabbingRequest(ImGuiID id, ImGuiItemFlags item_flag ImGuiContext& g = *GImGui; if ((move_flags & ImGuiNavMoveFlags_FocusApi) == 0) + { if (g.NavLayer != g.CurrentWindow->DC.NavLayerCurrent) return; - if (g.NavFocusScopeId != g.CurrentFocusScopeId) - return; + if (g.NavFocusScopeId != g.CurrentFocusScopeId) + return; + } // - Can always land on an item when using API call. // - Tabbing with _NavEnableKeyboard (space/enter/arrows): goes through every item. @@ -11598,7 +11777,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) if (window->Flags & ImGuiWindowFlags_NoNavInputs) { g.NavId = 0; - g.NavFocusScopeId = window->NavRootFocusScopeId; + SetNavFocusScope(window->NavRootFocusScopeId); return; } @@ -11617,7 +11796,7 @@ void ImGui::NavInitWindow(ImGuiWindow* window, bool force_reinit) else { g.NavId = window->NavLastIds[0]; - g.NavFocusScopeId = window->NavRootFocusScopeId; + SetNavFocusScope(window->NavRootFocusScopeId); } } @@ -11738,10 +11917,10 @@ static void ImGui::NavUpdate() g.NavActivateFlags = ImGuiActivateFlags_None; if (g.NavId != 0 && !g.NavDisableHighlight && !g.NavWindowingTarget && g.NavWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) { - const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate)); - const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, false)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, false))); - const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_KeypadEnter))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput)); - const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, false) || IsKeyPressed(ImGuiKey_KeypadEnter, false))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, false))); + const bool activate_down = (nav_keyboard_active && IsKeyDown(ImGuiKey_Space, ImGuiKeyOwner_None)) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_None)); + const bool activate_pressed = activate_down && ((nav_keyboard_active && IsKeyPressed(ImGuiKey_Space, ImGuiKeyOwner_None)) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadActivate, ImGuiKeyOwner_None))); + const bool input_down = (nav_keyboard_active && (IsKeyDown(ImGuiKey_Enter, ImGuiKeyOwner_None) || IsKeyDown(ImGuiKey_KeypadEnter, ImGuiKeyOwner_None))) || (nav_gamepad_active && IsKeyDown(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_None)); + const bool input_pressed = input_down && ((nav_keyboard_active && (IsKeyPressed(ImGuiKey_Enter, ImGuiKeyOwner_None) || IsKeyPressed(ImGuiKey_KeypadEnter, ImGuiKeyOwner_None))) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadInput, ImGuiKeyOwner_None))); if (g.ActiveId == 0 && activate_pressed) { g.NavActivateId = g.NavId; @@ -11755,13 +11934,22 @@ static void ImGui::NavUpdate() if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_down || input_down)) g.NavActivateDownId = g.NavId; if ((g.ActiveId == 0 || g.ActiveId == g.NavId) && (activate_pressed || input_pressed)) + { g.NavActivatePressedId = g.NavId; + NavHighlightActivated(g.NavId); + } } if (g.NavWindow && (g.NavWindow->Flags & ImGuiWindowFlags_NoNavInputs)) g.NavDisableHighlight = true; if (g.NavActivateId != 0) IM_ASSERT(g.NavActivateDownId == g.NavActivateId); + // Highlight + if (g.NavHighlightActivatedTimer > 0.0f) + g.NavHighlightActivatedTimer = ImMax(0.0f, g.NavHighlightActivatedTimer - io.DeltaTime); + if (g.NavHighlightActivatedTimer == 0.0f) + g.NavHighlightActivatedId = 0; + // Process programmatic activation request // FIXME-NAV: Those should eventually be queued (unlike focus they don't cancel each others) if (g.NavNextActivateId != 0) @@ -12085,6 +12273,8 @@ void ImGui::NavMoveRequestApplyResult() g.NavWindow = result->Window; g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; } + + // FIXME: Could become optional e.g. ImGuiNavMoveFlags_NoClearActiveId if we later want to apply navigation requests without altering active input. if (g.ActiveId != result->ID) ClearActiveID(); @@ -12153,15 +12343,14 @@ static void ImGui::NavUpdateCancelRequest() NavRestoreLayer(ImGuiNavLayer_Main); NavRestoreHighlightAfterMove(); } - else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->ParentWindow) + else if (g.NavWindow && g.NavWindow != g.NavWindow->RootWindow && !(g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->RootWindowForNav->ParentWindow) { // Exit child window - ImGuiWindow* child_window = g.NavWindow; - ImGuiWindow* parent_window = g.NavWindow->ParentWindow; + ImGuiWindow* child_window = g.NavWindow->RootWindowForNav; + ImGuiWindow* parent_window = child_window->ParentWindow; IM_ASSERT(child_window->ChildId != 0); - ImRect child_rect = child_window->Rect(); FocusWindow(parent_window); - SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_rect)); + SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_window->Rect())); NavRestoreHighlightAfterMove(); } else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) @@ -12456,28 +12645,33 @@ static void ImGui::NavUpdateWindowing() } // Keyboard: Press and Release ALT to toggle menu layer - // - Testing that only Alt is tested prevents Alt+Shift or AltGR from toggling menu layer. - // - AltGR is normally Alt+Ctrl but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl). But even on keyboards without AltGR we don't want Alt+Ctrl to open menu anyway. - if (nav_keyboard_active && IsKeyPressed(ImGuiMod_Alt, ImGuiKeyOwner_None)) - { - g.NavWindowingToggleLayer = true; - g.NavInputSource = ImGuiInputSource_Keyboard; - } + const ImGuiKey windowing_toggle_keys[] = { ImGuiKey_LeftAlt, ImGuiKey_RightAlt }; + for (ImGuiKey windowing_toggle_key : windowing_toggle_keys) + if (nav_keyboard_active && IsKeyPressed(windowing_toggle_key, ImGuiKeyOwner_None)) + { + g.NavWindowingToggleLayer = true; + g.NavWindowingToggleKey = windowing_toggle_key; + g.NavInputSource = ImGuiInputSource_Keyboard; + break; + } if (g.NavWindowingToggleLayer && g.NavInputSource == ImGuiInputSource_Keyboard) { // We cancel toggling nav layer when any text has been typed (generally while holding Alt). (See #370) // We cancel toggling nav layer when other modifiers are pressed. (See #4439) + // - AltGR is Alt+Ctrl on some layout but we can't reliably detect it (not all backends/systems/layout emit it as Alt+Ctrl). // We cancel toggling nav layer if an owner has claimed the key. - if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_None) == false) + if (io.InputQueueCharacters.Size > 0 || io.KeyCtrl || io.KeyShift || io.KeySuper) + g.NavWindowingToggleLayer = false; + if (TestKeyOwner(g.NavWindowingToggleKey, ImGuiKeyOwner_None) == false || TestKeyOwner(ImGuiMod_Alt, ImGuiKeyOwner_None) == false) g.NavWindowingToggleLayer = false; - // Apply layer toggle on release + // Apply layer toggle on Alt release // Important: as before version <18314 we lacked an explicit IO event for focus gain/loss, we also compare mouse validity to detect old backends clearing mouse pos on focus loss. - if (IsKeyReleased(ImGuiMod_Alt) && g.NavWindowingToggleLayer) + if (IsKeyReleased(g.NavWindowingToggleKey) && g.NavWindowingToggleLayer) if (g.ActiveId == 0 || g.ActiveIdAllowOverlap) if (IsMousePosValid(&io.MousePos) == IsMousePosValid(&io.MousePosPrev)) apply_toggle_layer = true; - if (!IsKeyDown(ImGuiMod_Alt)) + if (!IsKeyDown(g.NavWindowingToggleKey)) g.NavWindowingToggleLayer = false; } @@ -13790,7 +13984,7 @@ static void SetPlatformImeDataFn_DefaultImpl(ImGuiViewport*, ImGuiPlatformImeDat //----------------------------------------------------------------------------- // [SECTION] METRICS/DEBUGGER WINDOW //----------------------------------------------------------------------------- -// - RenderViewportThumbnail() [Internal] +// - DebugRenderViewportThumbnail() [Internal] // - RenderViewportsThumbnails() [Internal] // - DebugTextEncoding() // - MetricsHelpMarker() [Internal] @@ -13829,7 +14023,7 @@ void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* ImRect thumb_r = thumb_window->Rect(); ImRect title_r = thumb_window->TitleBarRect(); thumb_r = ImRect(ImTrunc(off + thumb_r.Min * scale), ImTrunc(off + thumb_r.Max * scale)); - title_r = ImRect(ImTrunc(off + title_r.Min * scale), ImTrunc(off + ImVec2(title_r.Max.x, title_r.Min.y) * scale) + ImVec2(0,5)); // Exaggerate title bar height + title_r = ImRect(ImTrunc(off + title_r.Min * scale), ImTrunc(off + ImVec2(title_r.Max.x, title_r.Min.y + title_r.GetHeight() * 3.0f) * scale)); // Exaggerate title bar height thumb_r.ClipWithFull(bb); title_r.ClipWithFull(bb); const bool window_is_focused = (g.NavWindow && thumb_window->RootWindowForTitleBarHighlight == g.NavWindow->RootWindowForTitleBarHighlight); @@ -13839,6 +14033,8 @@ void ImGui::DebugRenderViewportThumbnail(ImDrawList* draw_list, ImGuiViewportP* window->DrawList->AddText(g.Font, g.FontSize * 1.0f, title_r.Min, GetColorU32(ImGuiCol_Text, alpha_mul), thumb_window->Name, FindRenderedTextEnd(thumb_window->Name)); } draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border, alpha_mul)); + if (viewport->ID == g.DebugMetricsConfig.HighlightViewportID) + window->DrawList->AddRect(bb.Min, bb.Max, IM_COL32(255, 255, 0, 255)); } static void RenderViewportsThumbnails() @@ -13846,13 +14042,12 @@ static void RenderViewportsThumbnails() ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - // We don't display full monitor bounds (we could, but it often looks awkward), instead we display just enough to cover all of our viewports. float SCALE = 1.0f / 8.0f; - ImRect bb_full(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - for (ImGuiViewportP* viewport : g.Viewports) - bb_full.Add(viewport->GetMainRect()); + ImRect bb_full(g.Viewports[0]->Pos, g.Viewports[0]->Pos + g.Viewports[0]->Size); ImVec2 p = window->DC.CursorPos; ImVec2 off = p - bb_full.Min * SCALE; + + // Draw viewports for (ImGuiViewportP* viewport : g.Viewports) { ImRect viewport_draw_bb(off + (viewport->Pos) * SCALE, off + (viewport->Pos + viewport->Size) * SCALE); @@ -14095,7 +14290,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) MetricsHelpMarker("Will call the IM_DEBUG_BREAK() macro to break in debugger.\nWarning: If you don't have a debugger attached, this will probably crash."); if (Checkbox("Show Item Picker", &g.DebugItemPickerActive) && g.DebugItemPickerActive) DebugStartItemPicker(); - Checkbox("Show \"Debug Break\" buttons in other sections", &g.IO.ConfigDebugIsDebuggerPresent); + Checkbox("Show \"Debug Break\" buttons in other sections (io.ConfigDebugIsDebuggerPresent)", &g.IO.ConfigDebugIsDebuggerPresent); SeparatorText("Visualize"); @@ -14231,9 +14426,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) // Viewports if (TreeNode("Viewports", "Viewports (%d)", g.Viewports.Size)) { - Indent(GetTreeNodeToLabelSpacing()); - RenderViewportsThumbnails(); - Unindent(GetTreeNodeToLabelSpacing()); + SetNextItemOpen(true, ImGuiCond_Once); + if (TreeNode("Windows Minimap")) + { + RenderViewportsThumbnails(); + TreePop(); + } + cfg->HighlightViewportID = 0; + for (ImGuiViewportP* viewport : g.Viewports) DebugNodeViewport(viewport); TreePop(); @@ -14359,6 +14559,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) { ImGuiDebugAllocInfo* info = &g.DebugAllocInfo; Text("%d current allocations", info->TotalAllocCount - info->TotalFreeCount); + if (SmallButton("GC now")) { g.GcCompactAll = true; } Text("Recent frames with allocations:"); int buf_size = IM_ARRAYSIZE(info->LastEntriesBuf); for (int n = buf_size - 1; n >= 0; n--) @@ -14446,10 +14647,9 @@ void ImGui::ShowMetricsWindow(bool* p_open) ImGuiKeyRoutingTable* rt = &g.KeysRoutingTable; for (ImGuiKeyRoutingIndex idx = rt->Index[key - ImGuiKey_NamedKey_BEGIN]; idx != -1; ) { - char key_chord_name[64]; ImGuiKeyRoutingData* routing_data = &rt->Entries[idx]; ImGuiKeyChord key_chord = key | routing_data->Mods; - Text("%s: 0x%08X", GetKeyChordName(key_chord, key_chord_name, IM_ARRAYSIZE(key_chord_name)), routing_data->RoutingCurr); + Text("%s: 0x%08X (scored %d)", GetKeyChordName(key_chord), routing_data->RoutingCurr, routing_data->RoutingCurrScore); DebugLocateItemOnHover(routing_data->RoutingCurr); if (g.IO.ConfigDebugIsDebuggerPresent) { @@ -14501,6 +14701,14 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("NavActivateFlags: %04X", g.NavActivateFlags); Text("NavDisableHighlight: %d, NavDisableMouseHover: %d", g.NavDisableHighlight, g.NavDisableMouseHover); Text("NavFocusScopeId = 0x%08X", g.NavFocusScopeId); + Text("NavFocusRoute[] = "); + for (int path_n = g.NavFocusRoute.Size - 1; path_n >= 0; path_n--) + { + const ImGuiFocusScopeData& focus_scope = g.NavFocusRoute[path_n]; + SameLine(0.0f, 0.0f); + Text("0x%08X/", focus_scope.ID); + SetItemTooltip("In window \"%s\"", FindWindowByID(focus_scope.WindowID)->Name); + } Text("NavWindowingTarget: '%s'", g.NavWindowingTarget ? g.NavWindowingTarget->Name : "NULL"); Unindent(); @@ -14639,10 +14847,12 @@ void ImGui::DebugNodeColumns(ImGuiOldColumns* columns) static void FormatTextureIDForDebugDisplay(char* buf, int buf_size, ImTextureID tex_id) { + union { void* ptr; int integer; } tex_id_opaque; + memcpy(&tex_id_opaque, &tex_id, ImMin(sizeof(void*), sizeof(tex_id))); if (sizeof(tex_id) >= sizeof(void*)) - ImFormatString(buf, buf_size, "0x%p", (void*)*(intptr_t*)(void*)&tex_id); + ImFormatString(buf, buf_size, "0x%p", tex_id_opaque.ptr); else - ImFormatString(buf, buf_size, "0x%04X", *(int*)(void*)&tex_id); + ImFormatString(buf, buf_size, "0x%04X", tex_id_opaque.integer); } // [DEBUG] Display contents of ImDrawList @@ -14925,8 +15135,12 @@ void ImGui::DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label) void ImGui::DebugNodeViewport(ImGuiViewportP* viewport) { + ImGuiContext& g = *GImGui; SetNextItemOpen(true, ImGuiCond_Once); - if (TreeNode("viewport0", "Viewport #%d", 0)) + bool open = TreeNode("viewport0", "Viewport #%d", 0); + if (IsItemHovered()) + g.DebugMetricsConfig.HighlightViewportID = viewport->ID; + if (open) { ImGuiWindowFlags flags = viewport->Flags; BulletText("Main Pos: (%.0f,%.0f), Size: (%.0f,%.0f)\nWorkArea Offset Left: %.0f Top: %.0f, Right: %.0f, Bottom: %.0f", @@ -14990,9 +15204,10 @@ void ImGui::DebugNodeWindow(ImGuiWindow* window, const char* label) for (int layer = 0; layer < ImGuiNavLayer_COUNT; layer++) BulletText("NavPreferredScoringPosRel[%d] = {%.1f,%.1f)", layer, (pr[layer].x == FLT_MAX ? -99999.0f : pr[layer].x), (pr[layer].y == FLT_MAX ? -99999.0f : pr[layer].y)); // Display as 99999.0f so it looks neater. BulletText("NavLayersActiveMask: %X, NavLastChildNavWindow: %s", window->DC.NavLayersActiveMask, window->NavLastChildNavWindow ? window->NavLastChildNavWindow->Name : "NULL"); - if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } - if (window->ParentWindow != NULL) { DebugNodeWindow(window->ParentWindow, "ParentWindow"); } - if (window->DC.ChildWindows.Size > 0) { DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows"); } + if (window->RootWindow != window) { DebugNodeWindow(window->RootWindow, "RootWindow"); } + if (window->ParentWindow != NULL) { DebugNodeWindow(window->ParentWindow, "ParentWindow"); } + if (window->ParentWindowForFocusRoute != NULL) { DebugNodeWindow(window->ParentWindowForFocusRoute, "ParentWindowForFocusRoute"); } + if (window->DC.ChildWindows.Size > 0) { DebugNodeWindowsList(&window->DC.ChildWindows, "ChildWindows"); } if (window->ColumnsStorage.Size > 0 && TreeNode("Columns", "Columns sets (%d)", window->ColumnsStorage.Size)) { for (ImGuiOldColumns& columns : window->ColumnsStorage) @@ -15105,7 +15320,10 @@ void ImGui::ShowDebugLogWindow(bool* p_open) return; } - CheckboxFlags("All", &g.DebugLogFlags, ImGuiDebugLogFlags_EventMask_); + ImGuiDebugLogFlags all_enable_flags = ImGuiDebugLogFlags_EventMask_ & ~ImGuiDebugLogFlags_EventInputRouting; + CheckboxFlags("All", &g.DebugLogFlags, all_enable_flags); + SetItemTooltip("(except InputRouting which is spammy)"); + ShowDebugLogFlag("ActiveId", ImGuiDebugLogFlags_EventActiveId); ShowDebugLogFlag("Clipper", ImGuiDebugLogFlags_EventClipper); ShowDebugLogFlag("Focus", ImGuiDebugLogFlags_EventFocus); @@ -15113,6 +15331,7 @@ void ImGui::ShowDebugLogWindow(bool* p_open) ShowDebugLogFlag("Nav", ImGuiDebugLogFlags_EventNav); ShowDebugLogFlag("Popup", ImGuiDebugLogFlags_EventPopup); //ShowDebugLogFlag("Selection", ImGuiDebugLogFlags_EventSelection); + ShowDebugLogFlag("InputRouting", ImGuiDebugLogFlags_EventInputRouting); if (SmallButton("Clear")) { @@ -15204,6 +15423,7 @@ void ImGui::DebugLocateItem(ImGuiID target_id) g.DebugBreakInLocateId = false; } +// FIXME: Doesn't work over through a modal window, because they clear HoveredWindow. void ImGui::DebugLocateItemOnHover(ImGuiID target_id) { if (target_id == 0 || !IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenBlockedByPopup)) @@ -15476,9 +15696,6 @@ void ImGui::DebugLogV(const char*, va_list) {} void ImGui::ShowDebugLogWindow(bool*) {} void ImGui::ShowIDStackToolWindow(bool*) {} void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void*, const void*) {} -void ImGui::UpdateDebugToolItemPicker() {} -void ImGui::UpdateDebugToolStackQueries() {} -void ImGui::UpdateDebugToolFlashStyleColor() {} #endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS diff --git a/core/deps/imgui/imgui.h b/core/deps/imgui/imgui.h index bea90487f..a7ff9cb91 100644 --- a/core/deps/imgui/imgui.h +++ b/core/deps/imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.1 +// dear imgui, v1.90.3 // (headers) // Help: @@ -23,8 +23,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.90.1" -#define IMGUI_VERSION_NUM 19010 +#define IMGUI_VERSION "1.90.3" +#define IMGUI_VERSION_NUM 19030 #define IMGUI_HAS_TABLE /* @@ -89,6 +89,8 @@ Index of this file: #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) // Helper Macros - IM_FMTARGS, IM_FMTLIST: Apply printf-style warnings to our formatting functions. +// (MSVC provides an equivalent mechanism via SAL Annotations but it would require the macros in a different +// location. e.g. #include + void myprintf(_Printf_format_string_ const char* format, ...)) #if !defined(IMGUI_USE_STB_SPRINTF) && defined(__MINGW32__) && !defined(__clang__) #define IM_FMTARGS(FMT) __attribute__((format(gnu_printf, FMT, FMT+1))) #define IM_FMTLIST(FMT) __attribute__((format(gnu_printf, FMT, 0))) @@ -342,7 +344,7 @@ namespace ImGui // - Use child windows to begin into a self-contained independent scrolling/clipping regions within a host window. Child windows can embed their own child. // - Before 1.90 (November 2023), the "ImGuiChildFlags child_flags = 0" parameter was "bool border = false". // This API is backward compatible with old code, as we guarantee that ImGuiChildFlags_Border == true. - // Consider updating your old call sites: + // Consider updating your old code: // BeginChild("Name", size, false) -> Begin("Name", size, 0); or Begin("Name", size, ImGuiChildFlags_None); // BeginChild("Name", size, true) -> Begin("Name", size, ImGuiChildFlags_Border); // - Manual sizing (each axis can use a different setting e.g. ImVec2(0.0f, 400.0f)): @@ -1026,7 +1028,7 @@ enum ImGuiWindowFlags_ }; // Flags for ImGui::BeginChild() -// (Legacy: bot 0 must always correspond to ImGuiChildFlags_Border to be backward compatible with old API using 'bool border = false'. +// (Legacy: bit 0 must always correspond to ImGuiChildFlags_Border to be backward compatible with old API using 'bool border = false'. // About using AutoResizeX/AutoResizeY flags: // - May be combined with SetNextWindowSizeConstraints() to set a min/max size for each axis (see "Demo->Child->Auto-resize with Constraints"). // - Size measurement for a given axis is only performed when the child window is within visible boundaries, or is just appearing. @@ -1037,7 +1039,7 @@ enum ImGuiWindowFlags_ enum ImGuiChildFlags_ { ImGuiChildFlags_None = 0, - ImGuiChildFlags_Border = 1 << 0, // Show an outer border and enable WindowPadding. (Important: this is always == 1 == true for legacy reason) + ImGuiChildFlags_Border = 1 << 0, // Show an outer border and enable WindowPadding. (IMPORTANT: this is always == 1 == true for legacy reason) ImGuiChildFlags_AlwaysUseWindowPadding = 1 << 1, // Pad with style.WindowPadding even if no border are drawn (no padding by default for non-bordered child windows because it makes more sense) ImGuiChildFlags_ResizeX = 1 << 2, // Allow resize from right border (layout direction). Enable .ini saving (unless ImGuiWindowFlags_NoSavedSettings passed to window flags) ImGuiChildFlags_ResizeY = 1 << 3, // Allow resize from bottom border (layout direction). " @@ -1106,8 +1108,8 @@ enum ImGuiTreeNodeFlags_ }; // Flags for OpenPopup*(), BeginPopupContext*(), IsPopupOpen() functions. -// - To be backward compatible with older API which took an 'int mouse_button = 1' argument, we need to treat -// small flags values as a mouse button index, so we encode the mouse button in the first few bits of the flags. +// - To be backward compatible with older API which took an 'int mouse_button = 1' argument instead of 'ImGuiPopupFlags flags', +// we need to treat small flags values as a mouse button index, so we encode the mouse button in the first few bits of the flags. // It is therefore guaranteed to be legal to pass a mouse button index in ImGuiPopupFlags. // - For the same reason, we exceptionally default the ImGuiPopupFlags argument of BeginPopupContextXXX functions to 1 instead of 0. // IMPORTANT: because the default parameter is 1 (==ImGuiPopupFlags_MouseButtonRight), if you rely on the default parameter @@ -1121,10 +1123,12 @@ enum ImGuiPopupFlags_ ImGuiPopupFlags_MouseButtonMiddle = 2, // For BeginPopupContext*(): open on Middle Mouse release. Guaranteed to always be == 2 (same as ImGuiMouseButton_Middle) ImGuiPopupFlags_MouseButtonMask_ = 0x1F, ImGuiPopupFlags_MouseButtonDefault_ = 1, - ImGuiPopupFlags_NoOpenOverExistingPopup = 1 << 5, // For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack - ImGuiPopupFlags_NoOpenOverItems = 1 << 6, // For BeginPopupContextWindow(): don't return true when hovering items, only when hovering empty space - ImGuiPopupFlags_AnyPopupId = 1 << 7, // For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup. - ImGuiPopupFlags_AnyPopupLevel = 1 << 8, // For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level) + ImGuiPopupFlags_NoReopen = 1 << 5, // For OpenPopup*(), BeginPopupContext*(): don't reopen same popup if already open (won't reposition, won't reinitialize navigation) + //ImGuiPopupFlags_NoReopenAlwaysNavInit = 1 << 6, // For OpenPopup*(), BeginPopupContext*(): focus and initialize navigation even when not reopening. + ImGuiPopupFlags_NoOpenOverExistingPopup = 1 << 7, // For OpenPopup*(), BeginPopupContext*(): don't open if there's already a popup at the same level of the popup stack + ImGuiPopupFlags_NoOpenOverItems = 1 << 8, // For BeginPopupContextWindow(): don't return true when hovering items, only when hovering empty space + ImGuiPopupFlags_AnyPopupId = 1 << 10, // For IsPopupOpen(): ignore the ImGuiID parameter and test for any popup. + ImGuiPopupFlags_AnyPopupLevel = 1 << 11, // For IsPopupOpen(): search/test at any level of the popup stack (default test in the current level) ImGuiPopupFlags_AnyPopup = ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel, }; @@ -2188,11 +2192,7 @@ struct ImGuiIO int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. -#endif -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - void* ImeWindowHandle; // = NULL // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. -#else - void* _UnusedPadding; + //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. #endif //------------------------------------------------------------------ @@ -3124,6 +3124,7 @@ enum ImGuiViewportFlags_ // - Windows are generally trying to stay within the Work Area of their host viewport. struct ImGuiViewport { + ImGuiID ID; // Unique identifier for the viewport ImGuiViewportFlags Flags; // See ImGuiViewportFlags_ ImVec2 Pos; // Main Area: Position of the viewport (Dear ImGui coordinates are the same as OS desktop/native coordinates) ImVec2 Size; // Main Area: Size of the viewport. diff --git a/core/deps/imgui/imgui_demo.cpp b/core/deps/imgui/imgui_demo.cpp index 4a116922e..bbed74244 100644 --- a/core/deps/imgui/imgui_demo.cpp +++ b/core/deps/imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.1 +// dear imgui, v1.90.3 // (demo code) // Help: @@ -6997,19 +6997,19 @@ struct ExampleAppConsole { ClearLog(); for (int i = 0; i < History.Size; i++) - free(History[i]); + ImGui::MemFree(History[i]); } // Portable helpers static int Stricmp(const char* s1, const char* s2) { int d; while ((d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; } return d; } static int Strnicmp(const char* s1, const char* s2, int n) { int d = 0; while (n > 0 && (d = toupper(*s2) - toupper(*s1)) == 0 && *s1) { s1++; s2++; n--; } return d; } - static char* Strdup(const char* s) { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = malloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); } + static char* Strdup(const char* s) { IM_ASSERT(s); size_t len = strlen(s) + 1; void* buf = ImGui::MemAlloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)s, len); } static void Strtrim(char* s) { char* str_end = s + strlen(s); while (str_end > s && str_end[-1] == ' ') str_end--; *str_end = 0; } void ClearLog() { for (int i = 0; i < Items.Size; i++) - free(Items[i]); + ImGui::MemFree(Items[i]); Items.clear(); } @@ -7175,7 +7175,7 @@ struct ExampleAppConsole for (int i = History.Size - 1; i >= 0; i--) if (Stricmp(History[i], command_line) == 0) { - free(History[i]); + ImGui::MemFree(History[i]); History.erase(History.begin() + i); break; } diff --git a/core/deps/imgui/imgui_draw.cpp b/core/deps/imgui/imgui_draw.cpp index 40a840c46..ca1fe7d0f 100644 --- a/core/deps/imgui/imgui_draw.cpp +++ b/core/deps/imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.1 +// dear imgui, v1.90.3 // (drawing and font code) /* @@ -641,7 +641,7 @@ void ImDrawList::PrimReserve(int idx_count, int vtx_count) _IdxWritePtr = IdxBuffer.Data + idx_buffer_old_size; } -// Release the a number of reserved vertices/indices from the end of the last reservation made with PrimReserve(). +// Release the number of reserved vertices/indices from the end of the last reservation made with PrimReserve(). void ImDrawList::PrimUnreserve(int idx_count, int vtx_count) { IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0); diff --git a/core/deps/imgui/imgui_internal.h b/core/deps/imgui/imgui_internal.h index fcb693697..41a792de8 100644 --- a/core/deps/imgui/imgui_internal.h +++ b/core/deps/imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.1 +// dear imgui, v1.90.3 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -15,6 +15,8 @@ Index of this file: // [SECTION] Generic helpers // [SECTION] ImDrawList support // [SECTION] Widgets support: flags, enums, data structures +// [SECTION] Data types support +// [SECTION] Popup support // [SECTION] Inputs support // [SECTION] Clipper support // [SECTION] Navigation support @@ -234,6 +236,7 @@ namespace ImStb #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) +#define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Static Asserts #define IM_STATIC_ASSERT(_COND) static_assert(_COND, "") @@ -297,11 +300,11 @@ namespace ImStb #elif defined(__clang__) #define IM_DEBUG_BREAK() __builtin_debugtrap() #elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) -#define IM_DEBUG_BREAK() __asm__ volatile("int $0x03") +#define IM_DEBUG_BREAK() __asm__ volatile("int3;nop") #elif defined(__GNUC__) && defined(__thumb__) #define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xde01") #elif defined(__GNUC__) && defined(__arm__) && !defined(__thumb__) -#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xe7f001f0"); +#define IM_DEBUG_BREAK() __asm__ volatile(".inst 0xe7f001f0") #else #define IM_DEBUG_BREAK() IM_ASSERT(0) // It is expected that you define IM_DEBUG_BREAK() into something that will break nicely in a debugger! #endif @@ -689,9 +692,6 @@ struct ImPool int GetBufSize() const { return Buf.Size; } int GetMapSize() const { return Map.Data.Size; } // It is the map we need iterate to find valid items, since we don't have "alive" storage anywhere T* TryGetMapData(ImPoolIdx n) { int idx = Map.Data[n].val_i; if (idx == -1) return NULL; return GetByIndex(idx); } -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - int GetSize() { return GetMapSize(); } // For ImPlot: should use GetMapSize() from (IMGUI_VERSION_NUM >= 18304) -#endif }; // Helper: ImChunkStream<> @@ -985,43 +985,6 @@ enum ImGuiPlotType ImGuiPlotType_Histogram, }; -enum ImGuiPopupPositionPolicy -{ - ImGuiPopupPositionPolicy_Default, - ImGuiPopupPositionPolicy_ComboBox, - ImGuiPopupPositionPolicy_Tooltip, -}; - -struct ImGuiDataVarInfo -{ - ImGuiDataType Type; - ImU32 Count; // 1+ - ImU32 Offset; // Offset in parent structure - void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); } -}; - -struct ImGuiDataTypeTempStorage -{ - ImU8 Data[8]; // Can fit any data up to ImGuiDataType_COUNT -}; - -// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). -struct ImGuiDataTypeInfo -{ - size_t Size; // Size in bytes - const char* Name; // Short descriptive name for the type, for debugging - const char* PrintFmt; // Default printf format for the type - const char* ScanFmt; // Default scanf format for the type -}; - -// Extend ImGuiDataType_ -enum ImGuiDataTypePrivate_ -{ - ImGuiDataType_String = ImGuiDataType_COUNT + 1, - ImGuiDataType_Pointer, - ImGuiDataType_ID, -}; - // Stacked color modifier, backup of modified data so we can restore it struct ImGuiColorMod { @@ -1106,7 +1069,7 @@ struct IMGUI_API ImGuiInputTextState int CurLenW, CurLenA; // we need to maintain our buffer length in both UTF-8 and wchar format. UTF-8 length is valid even if TextA is not. ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. ImVector TextA; // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity. - ImVector InitialTextA; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector InitialTextA; // value to revert to when pressing Escape = backup of end-user buffer at the time of focus (in UTF-8, unaltered) bool TextAIsValid; // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument) int BufCapacityA; // end-user buffer capacity float ScrollX; // horizontal scrolling/offset @@ -1116,6 +1079,9 @@ struct IMGUI_API ImGuiInputTextState bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool Edited; // edited this frame ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. + bool ReloadUserBuf; // force a reload of user buf so it may be modified externally. may be automatic in future version. + int ReloadSelectionStart; // POSITIONS ARE IN IMWCHAR units *NOT* UTF-8 this is why this is not exposed yet. + int ReloadSelectionEnd; ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } void ClearText() { CurLenW = CurLenA = 0; TextW[0] = 0; TextA[0] = 0; CursorClamp(); } @@ -1133,21 +1099,16 @@ struct IMGUI_API ImGuiInputTextState int GetSelectionStart() const { return Stb.select_start; } int GetSelectionEnd() const { return Stb.select_end; } void SelectAll() { Stb.select_start = 0; Stb.cursor = Stb.select_end = CurLenW; Stb.has_preferred_x = 0; } -}; -// Storage for current popup stack -struct ImGuiPopupData -{ - ImGuiID PopupId; // Set on OpenPopup() - ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() - ImGuiWindow* BackupNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close - int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as "not any of layers" value - int OpenFrameCount; // Set on OpenPopup() - ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) - ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) - ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup + // Reload user buf (WIP #2890) + // If you modify underlying user-passed const char* while active you need to call this (InputText V2 may lift this) + // strcpy(my_buf, "hello"); + // if (ImGuiInputTextState* state = ImGui::GetInputTextState(id)) // id may be ImGui::GetItemID() is last item + // state->ReloadUserBufAndSelectAll(); + void ReloadUserBufAndSelectAll() { ReloadUserBuf = true; ReloadSelectionStart = 0; ReloadSelectionEnd = INT_MAX; } + void ReloadUserBufAndKeepSelection() { ReloadUserBuf = true; ReloadSelectionStart = Stb.select_start; ReloadSelectionEnd = Stb.select_end; } + void ReloadUserBufAndMoveToEnd() { ReloadUserBuf = true; ReloadSelectionStart = ReloadSelectionEnd = INT_MAX; } - ImGuiPopupData() { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; } }; enum ImGuiNextWindowDataFlags_ @@ -1194,9 +1155,10 @@ typedef ImS64 ImGuiSelectionUserData; enum ImGuiNextItemDataFlags_ { - ImGuiNextItemDataFlags_None = 0, - ImGuiNextItemDataFlags_HasWidth = 1 << 0, - ImGuiNextItemDataFlags_HasOpen = 1 << 1, + ImGuiNextItemDataFlags_None = 0, + ImGuiNextItemDataFlags_HasWidth = 1 << 0, + ImGuiNextItemDataFlags_HasOpen = 1 << 1, + ImGuiNextItemDataFlags_HasShortcut = 1 << 2, }; struct ImGuiNextItemData @@ -1204,10 +1166,11 @@ struct ImGuiNextItemData ImGuiNextItemDataFlags Flags; ImGuiItemFlags ItemFlags; // Currently only tested/used for ImGuiItemFlags_AllowOverlap. // Non-flags members are NOT cleared by ItemAdd() meaning they are still valid during NavProcessItem() - float Width; // Set by SetNextItemWidth() ImGuiSelectionUserData SelectionUserData; // Set by SetNextItemSelectionUserData() (note that NULL/0 is a valid value, we use -1 == ImGuiSelectionUserData_Invalid to mark invalid values) - ImGuiCond OpenCond; + float Width; // Set by SetNextItemWidth() + ImGuiKeyChord Shortcut; // Set by SetNextItemShortcut() bool OpenVal; // Set by SetNextItemOpen() + ImGuiCond OpenCond : 8; ImGuiNextItemData() { memset(this, 0, sizeof(*this)); SelectionUserData = -1; } inline void ClearFlags() { Flags = ImGuiNextItemDataFlags_None; ItemFlags = ImGuiItemFlags_None; } // Also cleared manually by ItemAdd()! @@ -1279,6 +1242,66 @@ struct ImGuiPtrOrIndex ImGuiPtrOrIndex(int index) { Ptr = NULL; Index = index; } }; +//----------------------------------------------------------------------------- +// [SECTION] Data types support +//----------------------------------------------------------------------------- + +struct ImGuiDataVarInfo +{ + ImGuiDataType Type; + ImU32 Count; // 1+ + ImU32 Offset; // Offset in parent structure + void* GetVarPtr(void* parent) const { return (void*)((unsigned char*)parent + Offset); } +}; + +struct ImGuiDataTypeTempStorage +{ + ImU8 Data[8]; // Can fit any data up to ImGuiDataType_COUNT +}; + +// Type information associated to one ImGuiDataType. Retrieve with DataTypeGetInfo(). +struct ImGuiDataTypeInfo +{ + size_t Size; // Size in bytes + const char* Name; // Short descriptive name for the type, for debugging + const char* PrintFmt; // Default printf format for the type + const char* ScanFmt; // Default scanf format for the type +}; + +// Extend ImGuiDataType_ +enum ImGuiDataTypePrivate_ +{ + ImGuiDataType_String = ImGuiDataType_COUNT + 1, + ImGuiDataType_Pointer, + ImGuiDataType_ID, +}; + +//----------------------------------------------------------------------------- +// [SECTION] Popup support +//----------------------------------------------------------------------------- + +enum ImGuiPopupPositionPolicy +{ + ImGuiPopupPositionPolicy_Default, + ImGuiPopupPositionPolicy_ComboBox, + ImGuiPopupPositionPolicy_Tooltip, +}; + +// Storage for popup stacks (g.OpenPopupStack and g.BeginPopupStack) +struct ImGuiPopupData +{ + ImGuiID PopupId; // Set on OpenPopup() + ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() + ImGuiWindow* BackupNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close + int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as "not any of layers" value + int OpenFrameCount; // Set on OpenPopup() + ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) + ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) + ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup + + ImGuiPopupData() { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; } +}; + //----------------------------------------------------------------------------- // [SECTION] Inputs support //----------------------------------------------------------------------------- @@ -1369,11 +1392,12 @@ struct ImGuiKeyRoutingData { ImGuiKeyRoutingIndex NextEntryIndex; ImU16 Mods; // Technically we'd only need 4-bits but for simplify we store ImGuiMod_ values which need 16-bits. ImGuiMod_Shortcut is already translated to Ctrl/Super. + ImU8 RoutingCurrScore; // [DEBUG] For debug display ImU8 RoutingNextScore; // Lower is better (0: perfect score) ImGuiID RoutingCurr; ImGuiID RoutingNext; - ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_None; } + ImGuiKeyRoutingData() { NextEntryIndex = -1; Mods = 0; RoutingCurrScore = RoutingNextScore = 255; RoutingCurr = RoutingNext = ImGuiKeyOwner_None; } }; // Routing table: maintain a desired owner for each possible key-chord (key + mods), and setup owner in NewFrame() when mods are matching. @@ -1401,17 +1425,19 @@ struct ImGuiKeyOwnerData }; // Flags for extended versions of IsKeyPressed(), IsMouseClicked(), Shortcut(), SetKeyOwner(), SetItemKeyOwner() -// Don't mistake with ImGuiInputTextFlags! (for ImGui::InputText() function) +// Don't mistake with ImGuiInputTextFlags! (which is for ImGui::InputText() function) enum ImGuiInputFlags_ { // Flags for IsKeyPressed(), IsKeyChordPressed(), IsMouseClicked(), Shortcut() ImGuiInputFlags_None = 0, - ImGuiInputFlags_Repeat = 1 << 0, // Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. + + // Repeat mode + ImGuiInputFlags_Repeat = 1 << 0, // Enable repeat. Return true on successive repeats. Default for legacy IsKeyPressed(). NOT Default for legacy IsMouseClicked(). MUST BE == 1. ImGuiInputFlags_RepeatRateDefault = 1 << 1, // Repeat rate: Regular (default) ImGuiInputFlags_RepeatRateNavMove = 1 << 2, // Repeat rate: Fast ImGuiInputFlags_RepeatRateNavTweak = 1 << 3, // Repeat rate: Faster - // Specify when repeating key pressed can be interrupted. + // Repeat mode: Specify when repeating key pressed can be interrupted. // In theory ImGuiInputFlags_RepeatUntilOtherKeyPress may be a desirable default, but it would break too many behavior so everything is opt-in. ImGuiInputFlags_RepeatUntilRelease = 1 << 4, // Stop repeating when released (default for all functions except Shortcut). This only exists to allow overriding Shortcut() default behavior. ImGuiInputFlags_RepeatUntilKeyModsChange = 1 << 5, // Stop repeating when released OR if keyboard mods are changed (default for Shortcut) @@ -1422,38 +1448,46 @@ enum ImGuiInputFlags_ ImGuiInputFlags_CondHovered = 1 << 8, // Only set if item is hovered (default to both) ImGuiInputFlags_CondActive = 1 << 9, // Only set if item is active (default to both) ImGuiInputFlags_CondDefault_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, - ImGuiInputFlags_CondMask_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, // Flags for SetKeyOwner(), SetItemKeyOwner() - ImGuiInputFlags_LockThisFrame = 1 << 10, // Access to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. - ImGuiInputFlags_LockUntilRelease = 1 << 11, // Access to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released. This is useful to make input-owner-aware code steal keys from non-input-owner-aware code. + // Locking is useful to make input-owner-aware code steal keys from non-input-owner-aware code. If all code is input-owner-aware locking would never be necessary. + ImGuiInputFlags_LockThisFrame = 1 << 10, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared at end of frame. + ImGuiInputFlags_LockUntilRelease = 1 << 11, // Further accesses to key data will require EXPLICIT owner ID (ImGuiKeyOwner_Any/0 will NOT accepted for polling). Cleared when the key is released or at end of each frame if key is released. // Routing policies for Shortcut() + low-level SetShortcutRouting() // - The general idea is that several callers register interest in a shortcut, and only one owner gets it. - // - When a policy (other than _RouteAlways) is set, Shortcut() will register itself with SetShortcutRouting(), + // Parent -> call Shortcut(Ctrl+S) // When Parent is focused, Parent gets the shortcut. + // Child1 -> call Shortcut(Ctrl+S) // When Child1 is focused, Child1 gets the shortcut (Child1 overrides Parent shortcuts) + // Child2 -> no call // When Child2 is focused, Parent gets the shortcut. + // The whole system is order independent, so if Child1 does it calls before Parent results will be identical. + // This is an important property as it facilitate working with foreign code or larger codebase. + // - Visualize registered routes in 'Metrics->Inputs' and submitted routes in 'Debug Log->InputRouting'. + // - When a policy (except for _RouteAlways *) is set, Shortcut() will register itself with SetShortcutRouting(), // allowing the system to decide where to route the input among other route-aware calls. - // - Shortcut() uses ImGuiInputFlags_RouteFocused by default: meaning that a simple Shortcut() poll - // will register a route and only succeed when parent window is in the focus stack and if no-one - // with a higher priority is claiming the shortcut. - // - Using ImGuiInputFlags_RouteAlways is roughly equivalent to doing e.g. IsKeyPressed(key) + testing mods. + // (* Using ImGuiInputFlags_RouteAlways is roughly equivalent to calling IsKeyChordPressed(key)). + // - Shortcut() uses ImGuiInputFlags_RouteFocused by default. Meaning that a Shortcut() call will register + // a route and only succeed when parent window is in the focus-stack and if no-one with a higher priority + // is claiming the same shortcut. + // - You can chain two unrelated windows in the focus stack using SetWindowParentWindowForFocusRoute(). // - Priorities: GlobalHigh > Focused (when owner is active item) > Global > Focused (when focused window) > GlobalLow. // - Can select only 1 policy among all available. - ImGuiInputFlags_RouteFocused = 1 << 12, // (Default) Register focused route: Accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window. - ImGuiInputFlags_RouteGlobalLow = 1 << 13, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority. - ImGuiInputFlags_RouteGlobal = 1 << 14, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText). - ImGuiInputFlags_RouteGlobalHigh = 1 << 15, // Register route globally (highest priority: unlikely you need to use that: will interfere with every active items) - ImGuiInputFlags_RouteMask_ = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteGlobalLow | ImGuiInputFlags_RouteGlobalHigh, // _Always not part of this! + ImGuiInputFlags_RouteFocused = 1 << 12, // (Default) Honor focus route: Accept inputs if window is in focus stack. Deep-most focused window takes inputs. ActiveId takes inputs over deep-most focused window. + ImGuiInputFlags_RouteGlobalLow = 1 << 13, // Register route globally (lowest priority: unless a focused window or active item registered the route) -> recommended Global priority IF you need a Global priority. + ImGuiInputFlags_RouteGlobal = 1 << 14, // Register route globally (medium priority: unless an active item registered the route, e.g. CTRL+A registered by InputText will take priority over this). + ImGuiInputFlags_RouteGlobalHigh = 1 << 15, // Register route globally (higher priority: unlikely you need to use that: will interfere with every active items, e.g. CTRL+A registered by InputText will be overriden by this) ImGuiInputFlags_RouteAlways = 1 << 16, // Do not register route, poll keys directly. + // Routing polices: extra options ImGuiInputFlags_RouteUnlessBgFocused= 1 << 17, // Global routes will not be applied if underlying background/void is focused (== no Dear ImGui windows are focused). Useful for overlay applications. - ImGuiInputFlags_RouteExtraMask_ = ImGuiInputFlags_RouteAlways | ImGuiInputFlags_RouteUnlessBgFocused, // [Internal] Mask of which function support which flags ImGuiInputFlags_RepeatRateMask_ = ImGuiInputFlags_RepeatRateDefault | ImGuiInputFlags_RepeatRateNavMove | ImGuiInputFlags_RepeatRateNavTweak, ImGuiInputFlags_RepeatUntilMask_ = ImGuiInputFlags_RepeatUntilRelease | ImGuiInputFlags_RepeatUntilKeyModsChange | ImGuiInputFlags_RepeatUntilKeyModsChangeFromNone | ImGuiInputFlags_RepeatUntilOtherKeyPress, ImGuiInputFlags_RepeatMask_ = ImGuiInputFlags_Repeat | ImGuiInputFlags_RepeatRateMask_ | ImGuiInputFlags_RepeatUntilMask_, + ImGuiInputFlags_CondMask_ = ImGuiInputFlags_CondHovered | ImGuiInputFlags_CondActive, + ImGuiInputFlags_RouteMask_ = ImGuiInputFlags_RouteFocused | ImGuiInputFlags_RouteGlobal | ImGuiInputFlags_RouteGlobalLow | ImGuiInputFlags_RouteGlobalHigh, // _Always not part of this! ImGuiInputFlags_SupportedByIsKeyPressed = ImGuiInputFlags_RepeatMask_, ImGuiInputFlags_SupportedByIsMouseClicked = ImGuiInputFlags_Repeat, - ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteExtraMask_, + ImGuiInputFlags_SupportedByShortcut = ImGuiInputFlags_RepeatMask_ | ImGuiInputFlags_RouteMask_ | ImGuiInputFlags_RouteAlways | ImGuiInputFlags_RouteUnlessBgFocused, ImGuiInputFlags_SupportedBySetKeyOwner = ImGuiInputFlags_LockThisFrame | ImGuiInputFlags_LockUntilRelease, ImGuiInputFlags_SupportedBySetItemKeyOwner = ImGuiInputFlags_SupportedBySetKeyOwner | ImGuiInputFlags_CondMask_, }; @@ -1499,6 +1533,7 @@ enum ImGuiActivateFlags_ ImGuiActivateFlags_PreferTweak = 1 << 1, // Favor activation for tweaking with arrows or gamepad (e.g. for Slider/Drag). Default for Space key and if keyboard is not used. ImGuiActivateFlags_TryToPreserveState = 1 << 2, // Request widget to preserve state if it can (e.g. InputText will try to preserve cursor/selection) ImGuiActivateFlags_FromTabbing = 1 << 3, // Activation requested by a tabbing request + ImGuiActivateFlags_FromShortcut = 1 << 4, // Activation requested by an item shortcut via SetNextItemShortcut() function. }; // Early work-in-progress API for ScrollToItem() @@ -1519,8 +1554,7 @@ enum ImGuiScrollFlags_ enum ImGuiNavHighlightFlags_ { ImGuiNavHighlightFlags_None = 0, - ImGuiNavHighlightFlags_TypeDefault = 1 << 0, - ImGuiNavHighlightFlags_TypeThin = 1 << 1, + ImGuiNavHighlightFlags_Compact = 1 << 1, // Compact highlight, no padding ImGuiNavHighlightFlags_AlwaysDraw = 1 << 2, // Draw rectangular highlight if (g.NavId == id) _even_ when using the mouse. ImGuiNavHighlightFlags_NoRounding = 1 << 3, }; @@ -1569,6 +1603,12 @@ struct ImGuiNavItemData void Clear() { Window = NULL; ID = FocusScopeId = 0; InFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; } }; +struct ImGuiFocusScopeData +{ + ImGuiID ID; + ImGuiID WindowID; +}; + //----------------------------------------------------------------------------- // [SECTION] Typing-select support //----------------------------------------------------------------------------- @@ -1788,8 +1828,9 @@ enum ImGuiDebugLogFlags_ ImGuiDebugLogFlags_EventClipper = 1 << 4, ImGuiDebugLogFlags_EventSelection = 1 << 5, ImGuiDebugLogFlags_EventIO = 1 << 6, + ImGuiDebugLogFlags_EventInputRouting = 1 << 7, - ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO, + ImGuiDebugLogFlags_EventMask_ = ImGuiDebugLogFlags_EventActiveId | ImGuiDebugLogFlags_EventFocus | ImGuiDebugLogFlags_EventPopup | ImGuiDebugLogFlags_EventNav | ImGuiDebugLogFlags_EventClipper | ImGuiDebugLogFlags_EventSelection | ImGuiDebugLogFlags_EventIO | ImGuiDebugLogFlags_EventInputRouting, ImGuiDebugLogFlags_OutputToTTY = 1 << 20, // Also send output to TTY ImGuiDebugLogFlags_OutputToTestEngine = 1 << 21, // Also send output to Test Engine }; @@ -1824,6 +1865,8 @@ struct ImGuiMetricsConfig bool ShowAtlasTintedWithTextColor = false; int ShowWindowsRectsType = -1; int ShowTablesRectsType = -1; + int HighlightMonitorIdx = -1; + ImGuiID HighlightViewportID = 0; }; struct ImGuiStackLevelInfo @@ -1937,10 +1980,11 @@ struct ImGuiContext bool ActiveIdHasBeenPressedBefore; // Track whether the active id led to a press (this is to allow changing between PressOnClick and PressOnRelease without pressing twice). Used by range_select branch. bool ActiveIdHasBeenEditedBefore; // Was the value associated to the widget Edited over the course of the Active state. bool ActiveIdHasBeenEditedThisFrame; + bool ActiveIdFromShortcut; + int ActiveIdMouseButton : 8; ImVec2 ActiveIdClickOffset; // Clicked offset from upper-left corner, if applicable (currently only set by ButtonBehavior) ImGuiWindow* ActiveIdWindow; ImGuiInputSource ActiveIdSource; // Activating source: ImGuiInputSource_Mouse OR ImGuiInputSource_Keyboard OR ImGuiInputSource_Gamepad - int ActiveIdMouseButton; ImGuiID ActiveIdPreviousFrame; bool ActiveIdPreviousFrameIsAlive; bool ActiveIdPreviousFrameHasBeenEditedBefore; @@ -1955,6 +1999,7 @@ struct ImGuiContext double LastKeyModsChangeTime; // Record the last time key mods changed (affect repeat delay when using shortcut logic) double LastKeyModsChangeFromNoneTime; // Record the last time key mods changed away from being 0 (affect repeat delay when using shortcut logic) double LastKeyboardKeyPressTime; // Record the last time a keyboard key (ignore mouse/gamepad ones) was pressed. + ImBitArrayForNamedKeys KeysMayBeCharInput; // Lookup to tell if a key can emit char input, see IsKeyChordPotentiallyCharInput(). sizeof() = 20 bytes ImGuiKeyOwnerData KeysOwnerData[ImGuiKey_NamedKey_COUNT]; ImGuiKeyRoutingTable KeysRoutingTable; ImU32 ActiveIdUsingNavDirMask; // Active widget will want to read those nav move requests (e.g. can activate a button and move away from it) @@ -1965,8 +2010,8 @@ struct ImGuiContext #endif // Next window/item data - ImGuiID CurrentFocusScopeId; // == g.FocusScopeStack.back() - ImGuiItemFlags CurrentItemFlags; // == g.ItemFlagsStack.back() + ImGuiID CurrentFocusScopeId; // Value for currently appending items == g.FocusScopeStack.back(). Not to be mistaken with g.NavFocusScopeId. + ImGuiItemFlags CurrentItemFlags; // Value for currently appending items == g.ItemFlagsStack.back() ImGuiID DebugLocateId; // Storage for DebugLocateItemOnHover() feature: this is read by ItemAdd() so we keep it in a hot/cached location ImGuiNextItemData NextItemData; // Storage for SetNextItem** functions ImGuiLastItemData LastItemData; // Storage for last submitted item (setup by ItemAdd) @@ -1974,18 +2019,16 @@ struct ImGuiContext bool DebugShowGroupRects; // Shared stacks - ImGuiCol DebugFlashStyleColorIdx; // (Keep close to ColorStack to share cache line) - ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() - ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() - ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() - ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() - ImVector ItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() - ImVector GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() - ImVector OpenPopupStack; // Which popups are open (persistent) - ImVector BeginPopupStack; // Which level of BeginPopup() we are in (reset every frame) - ImVector NavTreeNodeStack; // Stack for TreeNode() when a NavLeft requested is emitted. - - int BeginMenuCount; + ImGuiCol DebugFlashStyleColorIdx; // (Keep close to ColorStack to share cache line) + ImVector ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() + ImVector StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() + ImVector FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() + ImVector FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() + ImVector ItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() + ImVector GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() + ImVector OpenPopupStack; // Which popups are open (persistent) + ImVector BeginPopupStack; // Which level of BeginPopup() we are in (reset every frame) + ImVector NavTreeNodeStack; // Stack for TreeNode() when a NavLeft requested is emitted. // Viewports ImVector Viewports; // Active viewports (Size==1 in 'master' branch). Each viewports hold their copy of ImDrawData. @@ -1993,11 +2036,14 @@ struct ImGuiContext // Gamepad/keyboard Navigation ImGuiWindow* NavWindow; // Focused window for navigation. Could be called 'FocusedWindow' ImGuiID NavId; // Focused item for navigation - ImGuiID NavFocusScopeId; // Identify a selection scope (selection code often wants to "clear other items" when landing on an item of the selection set) + ImGuiID NavFocusScopeId; // Focused focus scope (e.g. selection code often wants to "clear other items" when landing on an item of the same scope) + ImVector NavFocusRoute; // Reversed copy focus scope stack for NavId (should contains NavFocusScopeId). This essentially follow the window->ParentWindowForFocusRoute chain. ImGuiID NavActivateId; // ~~ (g.ActiveId == 0) && (IsKeyPressed(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate)) ? NavId : 0, also set when calling ActivateItem() ImGuiID NavActivateDownId; // ~~ IsKeyDown(ImGuiKey_Space) || IsKeyDown(ImGuiKey_Enter) || IsKeyDown(ImGuiKey_NavGamepadActivate) ? NavId : 0 ImGuiID NavActivatePressedId; // ~~ IsKeyPressed(ImGuiKey_Space) || IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_NavGamepadActivate) ? NavId : 0 (no repeat) ImGuiActivateFlags NavActivateFlags; + ImGuiID NavHighlightActivatedId; + float NavHighlightActivatedTimer; ImGuiID NavJustMovedToId; // Just navigated to this id (result of a successfully MoveRequest). ImGuiID NavJustMovedToFocusScopeId; // Just navigated to this focus scope id (result of a successfully MoveRequest). ImGuiKeyChord NavJustMovedToKeyMods; @@ -2044,6 +2090,7 @@ struct ImGuiContext float NavWindowingTimer; float NavWindowingHighlightAlpha; bool NavWindowingToggleLayer; + ImGuiKey NavWindowingToggleKey; ImVec2 NavWindowingAccumDeltaPos; ImVec2 NavWindowingAccumDeltaSize; @@ -2107,6 +2154,8 @@ struct ImGuiContext ImGuiInputTextDeactivatedState InputTextDeactivatedState; ImFont InputTextPasswordFont; ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. + int BeginMenuDepth; + int BeginComboDepth; ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets ImGuiID ColorEditCurrentID; // Set temporarily while inside of the parent-most ColorEdit4/ColorPicker4 (because they call each others). ImGuiID ColorEditSavedID; // ID we are saving/restoring HS for @@ -2163,6 +2212,7 @@ struct ImGuiContext int LogDepthToExpandDefault; // Default/stored value for LogDepthMaxExpand if not specified in the LogXXX function call. // Debug Tools + // (some of the highly frequently used data are interleaved in other structures above: DebugBreakXXX fields, DebugHookIdInfo, DebugLocateId etc.) ImGuiDebugLogFlags DebugLogFlags; ImGuiTextBuffer DebugLogBuf; ImGuiTextIndex DebugLogIndex; @@ -2190,6 +2240,7 @@ struct ImGuiContext int WantCaptureKeyboardNextFrame; // " int WantTextInputNextFrame; ImVector TempBuffer; // Temporary text buffer + char TempKeychordName[64]; ImGuiContext(ImFontAtlas* shared_font_atlas) { @@ -2235,6 +2286,7 @@ struct ImGuiContext ActiveIdHasBeenPressedBefore = false; ActiveIdHasBeenEditedBefore = false; ActiveIdHasBeenEditedThisFrame = false; + ActiveIdFromShortcut = false; ActiveIdClickOffset = ImVec2(-1, -1); ActiveIdWindow = NULL; ActiveIdSource = ImGuiInputSource_None; @@ -2257,12 +2309,13 @@ struct ImGuiContext CurrentFocusScopeId = 0; CurrentItemFlags = ImGuiItemFlags_None; DebugShowGroupRects = false; - BeginMenuCount = 0; NavWindow = NULL; NavId = NavFocusScopeId = NavActivateId = NavActivateDownId = NavActivatePressedId = 0; NavJustMovedToId = NavJustMovedToFocusScopeId = NavNextActivateId = 0; NavActivateFlags = NavNextActivateFlags = ImGuiActivateFlags_None; + NavHighlightActivatedId = 0; + NavHighlightActivatedTimer = 0.0f; NavJustMovedToKeyMods = ImGuiMod_None; NavInputSource = ImGuiInputSource_Keyboard; NavLayer = ImGuiNavLayer_Main; @@ -2290,6 +2343,7 @@ struct ImGuiContext NavWindowingTarget = NavWindowingTargetAnim = NavWindowingListWindow = NULL; NavWindowingTimer = NavWindowingHighlightAlpha = 0.0f; NavWindowingToggleLayer = false; + NavWindowingToggleKey = ImGuiKey_None; DimBgRatio = 0.0f; @@ -2318,6 +2372,7 @@ struct ImGuiContext MouseStationaryTimer = 0.0f; TempInputId = 0; + BeginMenuDepth = BeginComboDepth = 0; ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; ColorEditCurrentID = ColorEditSavedID = 0; ColorEditSavedHue = ColorEditSavedSat = 0.0f; @@ -2376,6 +2431,7 @@ struct ImGuiContext FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; FramerateSecPerFrameAccum = 0.0f; WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; + memset(TempKeychordName, 0, sizeof(TempKeychordName)); } }; @@ -2530,6 +2586,7 @@ struct IMGUI_API ImGuiWindow ImGuiWindow* RootWindowPopupTree; // Point to ourself or first ancestor that is not a child window. Cross through popups parent<>child. ImGuiWindow* RootWindowForTitleBarHighlight; // Point to ourself or first ancestor which will display TitleBgActive color when this window is active. ImGuiWindow* RootWindowForNav; // Point to ourself or first ancestor which doesn't have the NavFlattened flag. + ImGuiWindow* ParentWindowForFocusRoute; // Set to manual link a window to its logical parent so that Shortcut() chain are honoerd (e.g. Tool linked to Document) ImGuiWindow* NavLastChildNavWindow; // When going to the menu bar, we remember the child window we came from. (This could probably be made implicit if we kept g.Windows sorted by last focused including child window.) ImGuiID NavLastIds[ImGuiNavLayer_COUNT]; // Last known NavId for this window, per layer (0/1) @@ -2943,6 +3000,7 @@ namespace ImGui IMGUI_API void SetWindowCollapsed(ImGuiWindow* window, bool collapsed, ImGuiCond cond = 0); IMGUI_API void SetWindowHitTestHole(ImGuiWindow* window, const ImVec2& pos, const ImVec2& size); IMGUI_API void SetWindowHiddenAndSkipItemsForCurrentFrame(ImGuiWindow* window); + inline void SetWindowParentWindowForFocusRoute(ImGuiWindow* window, ImGuiWindow* parent_window) { window->ParentWindowForFocusRoute = parent_window; } inline ImRect WindowRectAbsToRel(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x - off.x, r.Min.y - off.y, r.Max.x - off.x, r.Max.y - off.y); } inline ImRect WindowRectRelToAbs(ImGuiWindow* window, const ImRect& r) { ImVec2 off = window->DC.CursorStartPos; return ImRect(r.Min.x + off.x, r.Min.y + off.y, r.Max.x + off.x, r.Max.y + off.y); } inline ImVec2 WindowPosRelToAbs(ImGuiWindow* window, const ImVec2& p) { ImVec2 off = window->DC.CursorStartPos; return ImVec2(p.x + off.x, p.y + off.y); } @@ -3096,11 +3154,13 @@ namespace ImGui IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestApplyResult(); IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); + IMGUI_API void NavHighlightActivated(ImGuiID id); IMGUI_API void NavClearPreferredPosForAxis(ImGuiAxis axis); IMGUI_API void NavRestoreHighlightAfterMove(); IMGUI_API void NavUpdateCurrentWindowIsScrollPushableX(); IMGUI_API void SetNavWindow(ImGuiWindow* window); IMGUI_API void SetNavID(ImGuiID id, ImGuiNavLayer nav_layer, ImGuiID focus_scope_id, const ImRect& rect_rel); + IMGUI_API void SetNavFocusScope(ImGuiID focus_scope_id); // Focus/Activation // This should be part of a larger set of API: FocusItem(offset = -1), FocusItemByID(id), ActivateItem(offset = -1), ActivateItemByID(id) etc. which are @@ -3117,7 +3177,8 @@ namespace ImGui inline bool IsGamepadKey(ImGuiKey key) { return key >= ImGuiKey_Gamepad_BEGIN && key < ImGuiKey_Gamepad_END; } inline bool IsMouseKey(ImGuiKey key) { return key >= ImGuiKey_Mouse_BEGIN && key < ImGuiKey_Mouse_END; } inline bool IsAliasKey(ImGuiKey key) { return key >= ImGuiKey_Aliases_BEGIN && key < ImGuiKey_Aliases_END; } - inline ImGuiKeyChord ConvertShortcutMod(ImGuiKeyChord key_chord) { ImGuiContext& g = *GImGui; IM_ASSERT_PARANOID(key_chord & ImGuiMod_Shortcut); return (key_chord & ~ImGuiMod_Shortcut) | (g.IO.ConfigMacOSXBehaviors ? ImGuiMod_Super : ImGuiMod_Ctrl); } + inline bool IsModKey(ImGuiKey key) { return key >= ImGuiKey_LeftCtrl && key <= ImGuiKey_RightSuper; } + ImGuiKeyChord FixupKeyChord(ImGuiContext* ctx, ImGuiKeyChord key_chord); inline ImGuiKey ConvertSingleModFlagToKey(ImGuiContext* ctx, ImGuiKey key) { ImGuiContext& g = *ctx; @@ -3131,7 +3192,7 @@ namespace ImGui IMGUI_API ImGuiKeyData* GetKeyData(ImGuiContext* ctx, ImGuiKey key); inline ImGuiKeyData* GetKeyData(ImGuiKey key) { ImGuiContext& g = *GImGui; return GetKeyData(&g, key); } - IMGUI_API const char* GetKeyChordName(ImGuiKeyChord key_chord, char* out_buf, int out_buf_size); + IMGUI_API const char* GetKeyChordName(ImGuiKeyChord key_chord); inline ImGuiKey MouseButtonToKey(ImGuiMouseButton button) { IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); return (ImGuiKey)(ImGuiKey_MouseLeft + button); } IMGUI_API bool IsMouseDragPastThreshold(ImGuiMouseButton button, float lock_threshold = -1.0f); IMGUI_API ImVec2 GetKeyMagnitude2d(ImGuiKey key_left, ImGuiKey key_right, ImGuiKey key_up, ImGuiKey key_down); @@ -3188,8 +3249,9 @@ namespace ImGui // - IsKeyChordPressed() compares mods + call IsKeyPressed() -> function has no side-effect. // - Shortcut() submits a route then if currently can be routed calls IsKeyChordPressed() -> function has (desirable) side-effects. IMGUI_API bool IsKeyChordPressed(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags = 0); + IMGUI_API void SetNextItemShortcut(ImGuiKeyChord key_chord); IMGUI_API bool Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); - IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id = 0, ImGuiInputFlags flags = 0); + IMGUI_API bool SetShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags flags = 0); // owner_id needs to be explicit and cannot be 0 IMGUI_API bool TestShortcutRouting(ImGuiKeyChord key_chord, ImGuiID owner_id); IMGUI_API ImGuiKeyRoutingData* GetShortcutRoutingData(ImGuiKeyChord key_chord); @@ -3318,7 +3380,7 @@ namespace ImGui IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, float grid_step, ImVec2 grid_off, float rounding = 0.0f, ImDrawFlags flags = 0); - IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_TypeDefault); // Navigation highlight + IMGUI_API void RenderNavHighlight(const ImRect& bb, ImGuiID id, ImGuiNavHighlightFlags flags = ImGuiNavHighlightFlags_None); // Navigation highlight IMGUI_API const char* FindRenderedTextEnd(const char* text, const char* text_end = NULL); // Find the optional ## from which we stop displaying text. IMGUI_API void RenderMouseCursor(ImVec2 pos, float scale, ImGuiMouseCursor mouse_cursor, ImU32 col_fill, ImU32 col_border, ImU32 col_shadow); @@ -3450,13 +3512,12 @@ namespace ImGui inline void SetItemUsingMouseWheel() { SetItemKeyOwner(ImGuiKey_MouseWheelY); } // Changed in 1.89 inline bool TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags = 0) { return TreeNodeUpdateNextOpen(id, flags); } // Renamed in 1.89 - // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets that used FocusableItemRegister(): + // Refactored focus/nav/tabbing system in 1.82 and 1.84. If you have old/custom copy-and-pasted widgets which used FocusableItemRegister(): // (Old) IMGUI_VERSION_NUM < 18209: using 'ItemAdd(....)' and 'bool tab_focused = FocusableItemRegister(...)' - // (Old) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool tab_focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_Focused) != 0' - // (New) IMGUI_VERSION_NUM >= 18413: using 'ItemAdd(..., ImGuiItemFlags_Inputable)' and 'bool tab_focused = (GetItemStatusFlags() & ImGuiItemStatusFlags_FocusedTabbing) != 0 || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))' (WIP) - // Widget code are simplified as there's no need to call FocusableItemUnregister() while managing the transition from regular widget to TempInputText() - inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) { IM_ASSERT(0); IM_UNUSED(window); IM_UNUSED(id); return false; } // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() - inline void FocusableItemUnregister(ImGuiWindow* window) { IM_ASSERT(0); IM_UNUSED(window); } // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem + // (Old) IMGUI_VERSION_NUM >= 18209: using 'ItemAdd(..., ImGuiItemAddFlags_Focusable)' and 'bool tab_focused = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_Focused) != 0' + // (New) IMGUI_VERSION_NUM >= 18413: using 'ItemAdd(..., ImGuiItemFlags_Inputable)' and 'bool tab_focused = (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput))' + //inline bool FocusableItemRegister(ImGuiWindow* window, ImGuiID id) // -> pass ImGuiItemAddFlags_Inputable flag to ItemAdd() + //inline void FocusableItemUnregister(ImGuiWindow* window) // -> unnecessary: TempInputText() uses ImGuiInputTextFlags_MergedItem #endif #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO inline bool IsKeyPressedMap(ImGuiKey key, bool repeat = true) { IM_ASSERT(IsNamedKey(key)); return IsKeyPressed(key, repeat); } // Removed in 1.87: Mapping from named key is always identity! diff --git a/core/deps/imgui/imgui_tables.cpp b/core/deps/imgui/imgui_tables.cpp index eab542d8c..36c4c95b7 100644 --- a/core/deps/imgui/imgui_tables.cpp +++ b/core/deps/imgui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.1 +// dear imgui, v1.90.3 // (tables and columns code) /* @@ -3082,7 +3082,7 @@ void ImGui::TableHeader(const char* label) if ((table->RowFlags & ImGuiTableRowFlags_Headers) == 0) TableSetBgColor(ImGuiTableBgTarget_CellBg, GetColorU32(ImGuiCol_TableHeaderBg), table->CurrentColumn); } - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding); if (held) table->HeldHeaderColumn = (ImGuiTableColumnIdx)column_n; window->DC.CursorPos.y -= g.Style.ItemSpacing.y * 0.5f; diff --git a/core/deps/imgui/imgui_widgets.cpp b/core/deps/imgui/imgui_widgets.cpp index 4169a71bf..b5c5d2750 100644 --- a/core/deps/imgui/imgui_widgets.cpp +++ b/core/deps/imgui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.1 +// dear imgui, v1.90.3 // (widgets code) /* @@ -477,6 +477,9 @@ void ImGui::BulletTextV(const char* fmt, va_list args) // Frame N + RepeatDelay + RepeatRate*N true true - true //------------------------------------------------------------------------------------------------------------------------------------------------- +// FIXME: For refactor we could output flags, incl mouse hovered vs nav keyboard vs nav triggered etc. +// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' +// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool* out_held, ImGuiButtonFlags flags) { ImGuiContext& g = *GImGui; @@ -597,9 +600,9 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool g.NavDisableHighlight = true; } - // Gamepad/Keyboard navigation - // We report navigated item as hovered but we don't set g.HoveredId to not interfere with mouse. - if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover && (g.ActiveId == 0 || g.ActiveId == id || g.ActiveId == window->MoveId)) + // Gamepad/Keyboard handling + // We report navigated and navigation-activated items as hovered but we don't set g.HoveredId to not interfere with mouse. + if (g.NavId == id && !g.NavDisableHighlight && g.NavDisableMouseHover) if (!(flags & ImGuiButtonFlags_NoHoveredOnFocus)) hovered = true; if (g.NavActivateDownId == id) @@ -621,8 +624,10 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool pressed = true; SetActiveID(id, window); g.ActiveIdSource = g.NavInputSource; - if (!(flags & ImGuiButtonFlags_NoNavFocus)) + if (!(flags & ImGuiButtonFlags_NoNavFocus) && !(g.NavActivateFlags & ImGuiActivateFlags_FromShortcut)) SetFocusID(id, window); + if (g.NavActivateFlags & ImGuiActivateFlags_FromShortcut) + g.ActiveIdFromShortcut = true; } } @@ -690,13 +695,19 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool else if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) { // When activated using Nav, we hold on the ActiveID until activation button is released - if (g.NavActivateDownId != id) + if (g.NavActivateDownId == id) + held = true; // hovered == true not true as we are already likely hovered on direct activation. + else ClearActiveID(); } if (pressed) g.ActiveIdHasBeenPressedBefore = true; } + // Activation highlight (this may be a remote activation) + if (g.NavHighlightActivatedId == id) + hovered = true; + if (out_hovered) *out_hovered = hovered; if (out_held) *out_held = held; @@ -990,7 +1001,6 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6 // Click position in scrollbar normalized space (0.0f->1.0f) const float clicked_v_norm = ImSaturate((mouse_pos_v - scrollbar_pos_v) / scrollbar_size_v); - SetHoveredID(id); bool seek_absolute = false; if (g.ActiveIdIsJustActivated) @@ -1798,7 +1808,7 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags // This is essentially a specialized version of BeginPopupEx() char name[16]; - ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginPopupStack.Size); // Recycle windows based on depth + ImFormatString(name, IM_ARRAYSIZE(name), "##Combo_%02d", g.BeginComboDepth); // Recycle windows based on depth // Set position given a custom constraint (peak into expected window size so we can position it) // FIXME: This might be easier to express with an hypothetical SetNextWindowPosConstraints() function? @@ -1825,12 +1835,15 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above return false; } + g.BeginComboDepth++; return true; } void ImGui::EndCombo() { + ImGuiContext& g = *GImGui; EndPopup(); + g.BeginComboDepth--; } // Call directly after the BeginCombo/EndCombo block. The preview is designed to only host non-interactive elements @@ -2006,7 +2019,7 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*old_getter)(void* // - DataTypeGetInfo() // - DataTypeFormatString() // - DataTypeApplyOp() -// - DataTypeApplyOpFromText() +// - DataTypeApplyFromText() // - DataTypeCompare() // - DataTypeClamp() // - GetMinimumStepAtDecimalPrecision @@ -4199,27 +4212,32 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ float scroll_y = is_multiline ? draw_window->Scroll.y : FLT_MAX; + const bool init_reload_from_user_buf = (state != NULL && state->ReloadUserBuf); const bool init_changed_specs = (state != NULL && state->Stb.single_line != !is_multiline); // state != NULL means its our state. const bool init_make_active = (user_clicked || user_scroll_finish || input_requested_by_nav); const bool init_state = (init_make_active || user_scroll_active); - if ((init_state && g.ActiveId != id) || init_changed_specs) + if ((init_state && g.ActiveId != id) || init_changed_specs || init_reload_from_user_buf) { // Access state even if we don't own it yet. state = &g.InputTextState; state->CursorAnimReset(); + state->ReloadUserBuf = false; // Backup state of deactivating item so they'll have a chance to do a write to output buffer on the same frame they report IsItemDeactivatedAfterEdit (#4714) InputTextDeactivateHook(state->ID); - // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) - // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + // From the moment we focused we are normally ignoring the content of 'buf' (unless we are in read-only mode) const int buf_len = (int)strlen(buf); - state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. - memcpy(state->InitialTextA.Data, buf, buf_len + 1); + if (!init_reload_from_user_buf) + { + // Take a copy of the initial buffer value. + state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->InitialTextA.Data, buf, buf_len + 1); + } // Preserve cursor position and undo/redo stack if we come back to same widget - // FIXME: Since we reworked this on 2022/06, may want to differenciate recycle_cursor vs recycle_undostate? - bool recycle_state = (state->ID == id && !init_changed_specs); + // FIXME: Since we reworked this on 2022/06, may want to differentiate recycle_cursor vs recycle_undostate? + bool recycle_state = (state->ID == id && !init_changed_specs && !init_reload_from_user_buf); if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0))) recycle_state = false; @@ -4244,7 +4262,13 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ stb_textedit_initialize_state(&state->Stb, !is_multiline); } - if (!is_multiline) + if (init_reload_from_user_buf) + { + state->Stb.select_start = state->ReloadSelectionStart; + state->Stb.cursor = state->Stb.select_end = state->ReloadSelectionEnd; + state->CursorClamp(); + } + else if (!is_multiline) { if (flags & ImGuiInputTextFlags_AutoSelectAll) select_all = true; @@ -4274,6 +4298,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right); if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory)) g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down); + SetKeyOwner(ImGuiKey_Enter, id); + SetKeyOwner(ImGuiKey_KeypadEnter, id); SetKeyOwner(ImGuiKey_Home, id); SetKeyOwner(ImGuiKey_End, id); if (is_multiline) @@ -4283,8 +4309,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } if (is_osx) SetKeyOwner(ImGuiMod_Alt, id); - if (flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_AllowTabInput)) // Disable keyboard tabbing out as we will use the \t character. - SetShortcutRouting(ImGuiKey_Tab, id); } // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) @@ -4413,11 +4437,20 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336) // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes) - if ((flags & ImGuiInputTextFlags_AllowTabInput) && Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat) && !is_readonly) + if ((flags & ImGuiInputTextFlags_AllowTabInput) && !is_readonly) { - unsigned int c = '\t'; // Insert TAB - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) - state->OnKeyPressed((int)c); + if (Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat)) + { + unsigned int c = '\t'; // Insert TAB + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + state->OnKeyPressed((int)c); + } + // FIXME: Implement Shift+Tab + /* + if (Shortcut(ImGuiKey_Tab | ImGuiMod_Shift, id, ImGuiInputFlags_Repeat)) + { + } + */ } // Process regular text input (before we check for Return because using some IME will effectively send a Return?) @@ -6332,7 +6365,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l // Render const ImU32 text_col = GetColorU32(ImGuiCol_Text); - ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_TypeThin; + ImGuiNavHighlightFlags nav_highlight_flags = ImGuiNavHighlightFlags_Compact; if (display_frame) { // Framed type @@ -6641,7 +6674,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl RenderFrame(bb.Min, bb.Max, col, false, 0.0f); } if (g.NavId == id) - RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_TypeThin | ImGuiNavHighlightFlags_NoRounding); + RenderNavHighlight(bb, id, ImGuiNavHighlightFlags_Compact | ImGuiNavHighlightFlags_NoRounding); if (span_all_columns) { @@ -7521,6 +7554,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) PopItemFlag(); bool want_open = false; + bool want_open_nav_init = false; bool want_close = false; if (window->DC.LayoutType == ImGuiLayoutType_Vertical) // (window->Flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_ChildMenu)) { @@ -7563,8 +7597,9 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) want_open = true; if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open { - want_open = true; + want_open = want_open_nav_init = true; NavMoveRequestCancel(); + NavRestoreHighlightAfterMove(); } } else @@ -7596,13 +7631,13 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) if (want_open && !menu_is_open && g.OpenPopupStack.Size > g.BeginPopupStack.Size) { - // Don't reopen/recycle same menu level in the same frame, first close the other menu and yield for a frame. + // Don't reopen/recycle same menu level in the same frame if it is a different menu ID, first close the other menu and yield for a frame. OpenPopup(label); } else if (want_open) { menu_is_open = true; - OpenPopup(label); + OpenPopup(label, ImGuiPopupFlags_NoReopen);// | (want_open_nav_init ? ImGuiPopupFlags_NoReopenAlwaysNavInit : 0)); } if (menu_is_open) @@ -7614,6 +7649,14 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) PopStyleVar(); if (menu_is_open) { + // Implement what ImGuiPopupFlags_NoReopenAlwaysNavInit would do: + // Perform an init request in the case the popup was already open (via a previous mouse hover) + if (want_open && want_open_nav_init && !g.NavInitRequest) + { + FocusWindow(g.CurrentWindow, ImGuiFocusRequestFlags_UnlessBelowModal); + NavInitWindow(g.CurrentWindow, false); + } + // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu() // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck) g.LastItemData = last_item_in_parent; diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index af0f47edd..a46d48ca7 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -294,6 +294,7 @@ void VulkanContext::InitImgui() initInfo.Queue = (VkQueue)graphicsQueue; initInfo.PipelineCache = (VkPipelineCache)*pipelineCache; initInfo.DescriptorPool = (VkDescriptorPool)*descriptorPool; + initInfo.RenderPass = (VkRenderPass)*renderPass; initInfo.MinImageCount = 2; initInfo.ImageCount = GetSwapChainSize(); #ifdef VK_DEBUG @@ -306,7 +307,7 @@ void VulkanContext::InitImgui() }); #endif - if (!ImGui_ImplVulkan_Init(&initInfo, (VkRenderPass)*renderPass)) + if (!ImGui_ImplVulkan_Init(&initInfo)) { die("ImGui initialization failed"); } From 1b0781c680c4127a4b4df7c608d5e5a5a528e313 Mon Sep 17 00:00:00 2001 From: scribam Date: Tue, 20 Feb 2024 13:00:05 +0100 Subject: [PATCH 13/86] bsd: update segfault context for arm64 --- core/linux/context.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/linux/context.cpp b/core/linux/context.cpp index 2d5bd6230..74136a177 100644 --- a/core/linux/context.cpp +++ b/core/linux/context.cpp @@ -65,6 +65,15 @@ static void context_segfault(host_context_t* hostctx, void* segfault_ctx) #if defined(__APPLE__) bicopy(hostctx->pc, MCTX(->__ss.__pc)); bicopy(hostctx->x0, MCTX(->__ss.__x[0])); + #elif defined(__FreeBSD__) + bicopy(hostctx->pc, MCTX(.mc_gpregs.gp_elr)); + bicopy(hostctx->x0, MCTX(.mc_gpregs.gp_x[0])); + #elif defined(__NetBSD__) + bicopy(hostctx->pc, MCTX(.__gregs[_REG_ELR])); + bicopy(hostctx->x0, MCTX(.__gregs[_REG_X0])); + #elif defined(__OpenBSD__) + bicopy(hostctx->pc, MCTX(->sc_elr)); + bicopy(hostctx->x0, MCTX(->sc_x[0])); #else bicopy(hostctx->pc, MCTX(.pc)); bicopy(hostctx->x0, MCTX(.regs[0])); From 56cad843c992bf0d0e38ce538c78b68f8ca3586f Mon Sep 17 00:00:00 2001 From: scribam Date: Tue, 20 Feb 2024 13:00:05 +0100 Subject: [PATCH 14/86] ci: use cross-platform-actions/action to build on bsd systems --- .cirrus.yml | 14 ------------ .github/workflows/bsd.yml | 48 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 14 deletions(-) delete mode 100644 .cirrus.yml create mode 100644 .github/workflows/bsd.yml diff --git a/.cirrus.yml b/.cirrus.yml deleted file mode 100644 index 13328469d..000000000 --- a/.cirrus.yml +++ /dev/null @@ -1,14 +0,0 @@ -freebsd_instance: - image_family: freebsd-14-0 - -env: - CCACHE_DIR: /tmp/ccache - CIRRUS_CLONE_SUBMODULES: true - -task: - install_script: pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip mesa-libs miniupnpc ninja pkgconf pulseaudio sdl2 - ccache_cache: - folder: /tmp/ccache - script: - - cmake -B build -DCMAKE_BUILD_TYPE=Release -G Ninja - - cmake --build build --config Release diff --git a/.github/workflows/bsd.yml b/.github/workflows/bsd.yml new file mode 100644 index 000000000..0509197c5 --- /dev/null +++ b/.github/workflows/bsd.yml @@ -0,0 +1,48 @@ +name: BSD CI + +on: [push, pull_request] + +jobs: + build: + env: + CCACHE_DIR: ${{ github.workspace }}/.ccache + runs-on: ubuntu-latest + strategy: + matrix: + operating_system: [ freebsd, netbsd, openbsd ] + architecture: [ arm64, x86-64 ] + include: + - operating_system: freebsd + version: '14.0' + pkginstall: sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip miniupnpc ninja pkgconf pulseaudio sdl2 + - operating_system: netbsd + version: '9.3' + pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache cmake gcc12 git libao libzip miniupnpc ninja-build pkgconf pulseaudio SDL2 && export PATH=/usr/pkg/gcc12/bin:$PATH + - operating_system: openbsd + version: '7.4' + pkginstall: sudo pkg_add ccache cmake git libao libzip miniupnpc ninja pkgconf pulseaudio sdl2 + exclude: + - architecture: arm64 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + + - uses: actions/cache@v4 + with: + path: ${{ env.CCACHE_DIR }} + key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ github.sha }} + restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}- + + - uses: cross-platform-actions/action@v0.23.0 + with: + operating_system: ${{ matrix.operating_system }} + architecture: ${{ matrix.architecture }} + version: ${{ matrix.version }} + environment_variables: CCACHE_DIR + run: | + ${{ matrix.pkginstall }} + cmake -B build -DCMAKE_BUILD_TYPE=Release -G Ninja + cmake --build build --config Release From b71541f8dda8503906aa5137c3a15ff155f5be35 Mon Sep 17 00:00:00 2001 From: scribam Date: Wed, 21 Feb 2024 21:45:17 +0100 Subject: [PATCH 15/86] cmake: improved parallelism in msbuild --- .github/workflows/uwp.yml | 2 +- CMakeLists.txt | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/uwp.yml b/.github/workflows/uwp.yml index 3616be790..8c7450a1b 100644 --- a/.github/workflows/uwp.yml +++ b/.github/workflows/uwp.yml @@ -15,7 +15,7 @@ jobs: - name: CMake run: | cmake -B build -DCMAKE_BUILD_TYPE=Release -G "Visual Studio 17 2022" -A x64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0.22000.0 - cmake --build build --config Release --parallel 2 + cmake --build build --config Release -- /m shell: cmd - uses: ilammy/msvc-dev-cmd@v1 diff --git a/CMakeLists.txt b/CMakeLists.txt index 24a141659..49f312f2e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,8 +186,13 @@ elseif(ANDROID) set(CMAKE_ANDROID_STL_TYPE "c++_static") elseif(WIN32) add_executable(${PROJECT_NAME} WIN32 core/emulator.cpp) - if(MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") + if(CMAKE_GENERATOR MATCHES "Visual Studio") + if(NOT CMAKE_VS_GLOBALS MATCHES "(^|;)UseMultiToolTask=") + list(APPEND CMAKE_VS_GLOBALS UseMultiToolTask=true) + endif() + if(NOT CMAKE_VS_GLOBALS MATCHES "(^|;)EnforceProcessCountAcrossBuilds=") + list(APPEND CMAKE_VS_GLOBALS EnforceProcessCountAcrossBuilds=true) + endif() set_property(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME}) endif() elseif(APPLE) From 4667d17d781eef1755eb4cf7ad09003b1da61fc4 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 24 Feb 2024 19:42:41 +0100 Subject: [PATCH 16/86] naomi: fix shaktam inputs Issue #1420 --- core/hw/naomi/naomi_roms.cpp | 3 +++ core/hw/naomi/naomi_roms_input.h | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/core/hw/naomi/naomi_roms.cpp b/core/hw/naomi/naomi_roms.cpp index afb2135dc..d7c5fc9ab 100644 --- a/core/hw/naomi/naomi_roms.cpp +++ b/core/hw/naomi/naomi_roms.cpp @@ -5792,6 +5792,7 @@ const Game Games[] = { NULL, 0, 0 }, }, "gds-0002b", + &shaktam_inputs, }, // Shakatto Tambourine Cho Powerup Chu (2K1 AUT) { @@ -5808,6 +5809,7 @@ const Game Games[] = { NULL, 0, 0 }, }, "gds-0016", + &shaktam_inputs, }, // Shakatto Tambourine Motto Norinori Shinkyoku Tsuika (2K1 SPR) { @@ -5824,6 +5826,7 @@ const Game Games[] = { NULL, 0, 0 }, }, "gds-0013", + &shaktam_inputs, }, // Shikigami No Shiro II / The Castle of Shikigami II { diff --git a/core/hw/naomi/naomi_roms_input.h b/core/hw/naomi/naomi_roms_input.h index 55c4640c6..b858808d3 100644 --- a/core/hw/naomi/naomi_roms_input.h +++ b/core/hw/naomi/naomi_roms_input.h @@ -727,3 +727,21 @@ static InputDescriptors crackindj_inputs = { { "FADER", Full, 0, true }, }, }; + +static InputDescriptors shaktam_inputs = { + { + NAO_START_DESC + NAO_BASE_BTN_DESC + { NAOMI_BTN0_KEY, "SHAKE L" }, + { NAOMI_BTN1_KEY, "SHAKE R" }, + { NAOMI_BTN2_KEY, "KNOCK", NAOMI_DOWN_KEY }, + { NAOMI_DOWN_KEY, "DOWN", NAOMI_LEFT_KEY }, + { NAOMI_UP_KEY, "UP", NAOMI_RIGHT_KEY }, + }, + { + { "TAMBOURINE X", Full, 0 }, + { "TAMBOURINE Y", Full, 1 }, + { "", Full, 2 }, // unused but P2 starts at axis 4 + { "", Full, 3 }, // unused but P2 starts at axis 4 + }, +}; From 713739bbc7ab87d6be517ede54ee16d80b5bc1d0 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 24 Feb 2024 21:33:40 +0100 Subject: [PATCH 17/86] sdl: support multiple mice when allowed by the platform --- core/sdl/sdl.cpp | 63 +++++++++++++++++++++++++----------------- core/sdl/sdl_gamepad.h | 12 ++++++-- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index da9164107..85cf106a2 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -37,7 +37,7 @@ static u32 windowFlags; #define WINDOW_WIDTH 640 #define WINDOW_HEIGHT 480 -static std::shared_ptr sdl_mouse; +static std::unordered_map> sdl_mice; static std::shared_ptr sdl_keyboard; static bool window_fullscreen; static bool window_maximized; @@ -140,14 +140,15 @@ static void emuEventCallback(Event event, void *) static void checkRawInput() { #if defined(_WIN32) && !defined(TARGET_UWP) - if ((bool)config::UseRawInput != (bool)sdl_mouse) + if ((bool)config::UseRawInput != (bool)sdl_keyboard) return; if (config::UseRawInput) { GamepadDevice::Unregister(sdl_keyboard); sdl_keyboard = nullptr; - GamepadDevice::Unregister(sdl_mouse); - sdl_mouse = nullptr; + for (auto& it : sdl_mice) + GamepadDevice::Unregister(it.second); + sdl_mice.clear(); rawinput::init(); } else @@ -155,8 +156,6 @@ static void checkRawInput() rawinput::term(); sdl_keyboard = std::make_shared(0); GamepadDevice::Register(sdl_keyboard); - sdl_mouse = std::make_shared(); - GamepadDevice::Register(sdl_mouse); } #else if (!sdl_keyboard) @@ -164,11 +163,6 @@ static void checkRawInput() sdl_keyboard = std::make_shared(0); GamepadDevice::Register(sdl_keyboard); } - if (!sdl_mouse) - { - sdl_mouse = std::make_shared(); - GamepadDevice::Register(sdl_mouse); - } #endif } @@ -251,6 +245,17 @@ inline void SDLMouse::setAbsPos(int x, int y) { Mouse::setAbsPos(x, y, width, height); } +static std::shared_ptr getMouse(u64 mouseId) +{ + auto& mouse = sdl_mice[mouseId]; + if (mouse == nullptr) + { + mouse = std::make_shared(mouseId); + GamepadDevice::Register(mouse); + } + return mouse; +} + void input_sdl_handle() { SDLGamepad::UpdateRumble(); @@ -422,10 +427,11 @@ void input_sdl_handle() checkRawInput(); if (!config::UseRawInput) { + auto mouse = getMouse(event.motion.which); if (mouseCaptured && gameRunning) - sdl_mouse->setRelPos(event.motion.xrel, event.motion.yrel); + mouse->setRelPos(event.motion.xrel, event.motion.yrel); else - sdl_mouse->setAbsPos(event.motion.x, event.motion.y); + mouse->setAbsPos(event.motion.x, event.motion.y); } else if (mouseCaptured && gameRunning) { @@ -451,24 +457,25 @@ void input_sdl_handle() checkRawInput(); if (!config::UseRawInput) { + auto mouse = getMouse(event.button.which); if (!mouseCaptured || !gameRunning) - sdl_mouse->setAbsPos(event.button.x, event.button.y); + mouse->setAbsPos(event.button.x, event.button.y); bool pressed = event.button.state == SDL_PRESSED; switch (event.button.button) { case SDL_BUTTON_LEFT: - sdl_mouse->setButton(Mouse::LEFT_BUTTON, pressed); + mouse->setButton(Mouse::LEFT_BUTTON, pressed); break; case SDL_BUTTON_RIGHT: - sdl_mouse->setButton(Mouse::RIGHT_BUTTON, pressed); + mouse->setButton(Mouse::RIGHT_BUTTON, pressed); break; case SDL_BUTTON_MIDDLE: - sdl_mouse->setButton(Mouse::MIDDLE_BUTTON, pressed); + mouse->setButton(Mouse::MIDDLE_BUTTON, pressed); break; case SDL_BUTTON_X1: - sdl_mouse->setButton(Mouse::BUTTON_4, pressed); + mouse->setButton(Mouse::BUTTON_4, pressed); break; case SDL_BUTTON_X2: - sdl_mouse->setButton(Mouse::BUTTON_5, pressed); + mouse->setButton(Mouse::BUTTON_5, pressed); break; } } @@ -478,8 +485,10 @@ void input_sdl_handle() case SDL_MOUSEWHEEL: gui_set_mouse_wheel(-event.wheel.y * 35); checkRawInput(); - if (!config::UseRawInput) - sdl_mouse->setWheel(-event.wheel.y); + if (!config::UseRawInput) { + auto mouse = getMouse(event.wheel.which); + mouse->setWheel(-event.wheel.y); + } break; case SDL_JOYDEVICEADDED: @@ -498,6 +507,7 @@ void input_sdl_handle() case SDL_FINGERDOWN: case SDL_FINGERMOTION: { + auto mouse = getMouse(0); int x = event.tfinger.x * settings.display.width; int y = event.tfinger.y * settings.display.height; gui_set_mouse_position(x, y); @@ -505,24 +515,25 @@ void input_sdl_handle() { int dx = event.tfinger.dx * settings.display.width; int dy = event.tfinger.dy * settings.display.height; - sdl_mouse->setRelPos(dx, dy); + mouse->setRelPos(dx, dy); } else - sdl_mouse->setAbsPos(x, y); + mouse->setAbsPos(x, y); if (event.type == SDL_FINGERDOWN) { - sdl_mouse->setButton(Mouse::LEFT_BUTTON, true); + mouse->setButton(Mouse::LEFT_BUTTON, true); gui_set_mouse_button(0, true); } } break; case SDL_FINGERUP: { + auto mouse = getMouse(0); int x = event.tfinger.x * settings.display.width; int y = event.tfinger.y * settings.display.height; gui_set_mouse_position(x, y); gui_set_mouse_button(0, false); - sdl_mouse->setAbsPos(x, y); - sdl_mouse->setButton(Mouse::LEFT_BUTTON, false); + mouse->setAbsPos(x, y); + mouse->setButton(Mouse::LEFT_BUTTON, false); } break; } diff --git a/core/sdl/sdl_gamepad.h b/core/sdl/sdl_gamepad.h index 33d9e83dd..6bb9e7f99 100644 --- a/core/sdl/sdl_gamepad.h +++ b/core/sdl/sdl_gamepad.h @@ -426,10 +426,16 @@ std::map> SDLGamepad::sdl_gamepads; class SDLMouse : public Mouse { public: - SDLMouse() : Mouse("SDL") + SDLMouse(u64 mouseId) : Mouse("SDL") { - this->_name = "Default Mouse"; - this->_unique_id = "sdl_mouse"; + if (mouseId == 0) { + this->_name = "Default Mouse"; + this->_unique_id = "sdl_mouse"; + } + else { + this->_name = "Mouse " + std::to_string(mouseId); + this->_unique_id = "sdl_mouse_" + std::to_string(mouseId); + } loadMapping(); } From ba12c01ab851a0653751da069bc96b68396acdf5 Mon Sep 17 00:00:00 2001 From: scribam Date: Sat, 24 Feb 2024 13:36:14 +0100 Subject: [PATCH 18/86] deps: update imgui to version 1.90.4 --- core/deps/imgui/imgui.cpp | 78 +++++++++++++++++++------------ core/deps/imgui/imgui.h | 12 +++-- core/deps/imgui/imgui_demo.cpp | 50 +++++++++++++++++--- core/deps/imgui/imgui_draw.cpp | 10 ++-- core/deps/imgui/imgui_internal.h | 8 ++-- core/deps/imgui/imgui_tables.cpp | 76 ++++++++++++++++++++---------- core/deps/imgui/imgui_widgets.cpp | 2 +- 7 files changed, 158 insertions(+), 78 deletions(-) diff --git a/core/deps/imgui/imgui.cpp b/core/deps/imgui/imgui.cpp index 51cfac372..a309d6836 100644 --- a/core/deps/imgui/imgui.cpp +++ b/core/deps/imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.3 +// dear imgui, v1.90.4 // (main code and documentation) // Help: @@ -917,7 +917,7 @@ CODE Q: How can I easily use icons in my application? Q: How can I load multiple fonts? Q: How can I display and input non-Latin characters such as Chinese, Japanese, Korean, Cyrillic? - >> See https://www.dearimgui.com/faq and https://github.com/ocornut/imgui/edit/master/docs/FONTS.md + >> See https://www.dearimgui.com/faq and https://github.com/ocornut/imgui/blob/master/docs/FONTS.md Q&A: Concerns ============= @@ -3047,13 +3047,14 @@ const ImVec4& ImGui::GetStyleColorVec4(ImGuiCol idx) return style.Colors[idx]; } -ImU32 ImGui::GetColorU32(ImU32 col) +ImU32 ImGui::GetColorU32(ImU32 col, float alpha_mul) { ImGuiStyle& style = GImGui->Style; - if (style.Alpha >= 1.0f) + alpha_mul *= style.Alpha; + if (alpha_mul >= 1.0f) return col; ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; - a = (ImU32)(a * style.Alpha); // We don't need to clamp 0..255 because Style.Alpha is in 0..1 range. + a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range. return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); } @@ -5003,7 +5004,7 @@ static void ImGui::RenderDimmedBackgrounds() { // Draw dimming behind modal or a begin stack child, whichever comes first in draw order. ImGuiWindow* dim_behind_window = FindBottomMostVisibleWindowWithinBeginStack(modal_window); - RenderDimmedBackgroundBehindWindow(dim_behind_window, GetColorU32(ImGuiCol_ModalWindowDimBg, g.DimBgRatio)); + RenderDimmedBackgroundBehindWindow(dim_behind_window, GetColorU32(modal_window->DC.ModalDimBgColor, g.DimBgRatio)); } else if (dim_bg_for_window_list) { @@ -5677,22 +5678,25 @@ static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags) static inline ImVec2 CalcWindowMinSize(ImGuiWindow* window) { - // Popups, menus and childs bypass style.WindowMinSize by default, but we give then a non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) - // FIXME: the if/else could probably be removed, "reduce artifacts" section for all windows. + // We give windows non-zero minimum size to facilitate understanding problematic cases (e.g. empty popups) + // FIXME: Essentially we want to restrict manual resizing to WindowMinSize+Decoration, and allow api resizing to be smaller. + // Perhaps should tend further a neater test for this. ImGuiContext& g = *GImGui; ImVec2 size_min; - if (window->Flags & (ImGuiWindowFlags_Popup | ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_ChildWindow)) + if ((window->Flags & ImGuiWindowFlags_ChildWindow) && !(window->Flags & ImGuiWindowFlags_Popup)) { size_min.x = (window->ChildFlags & ImGuiChildFlags_ResizeX) ? g.Style.WindowMinSize.x : 4.0f; size_min.y = (window->ChildFlags & ImGuiChildFlags_ResizeY) ? g.Style.WindowMinSize.y : 4.0f; } else { - ImGuiWindow* window_for_height = window; size_min.x = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.x : 4.0f; size_min.y = ((window->Flags & ImGuiWindowFlags_AlwaysAutoResize) == 0) ? g.Style.WindowMinSize.y : 4.0f; - size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); // Reduce artifacts with very small windows } + + // Reduce artifacts with very small windows + ImGuiWindow* window_for_height = window; + size_min.y = ImMax(size_min.y, window_for_height->TitleBarHeight() + window_for_height->MenuBarHeight() + ImMax(0.0f, g.Style.WindowRounding - 1.0f)); return size_min; } @@ -6758,7 +6762,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Handle manual resize: Resize Grips, Borders, Gamepad int border_hovered = -1, border_held = -1; ImU32 resize_grip_col[4] = {}; - const int resize_grip_count = (window->Flags & ImGuiWindowFlags_ChildWindow) ? 0 : g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it. + const int resize_grip_count = ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup)) ? 0 : g.IO.ConfigWindowsResizeFromEdges ? 2 : 1; // Allow resize from lower-left if we have the mouse cursor feedback for it. const float resize_grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.10f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); if (!window->Collapsed) if (int auto_fit_mask = UpdateWindowManualResize(window, size_auto_fit, &border_hovered, &border_held, resize_grip_count, &resize_grip_col[0], visibility_rect)) @@ -6958,6 +6962,8 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.TextWrapPos = -1.0f; // disabled window->DC.ItemWidthStack.resize(0); window->DC.TextWrapPosStack.resize(0); + if (flags & ImGuiWindowFlags_Modal) + window->DC.ModalDimBgColor = ColorConvertFloat4ToU32(GetStyleColorVec4(ImGuiCol_ModalWindowDimBg)); if (window->AutoFitFramesX > 0) window->AutoFitFramesX--; @@ -10798,7 +10804,7 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) else { // Reopen: close child popups if any, then flag popup for open/reopen (set position, focus, init navigation) - ClosePopupToLevel(current_stack_size, false); + ClosePopupToLevel(current_stack_size, true); g.OpenPopupStack.push_back(popup_ref); } @@ -11519,25 +11525,28 @@ static void ImGui::NavProcessItem() // Process Move Request (scoring for navigation) // FIXME-NAV: Consider policy for double scoring (scoring from NavScoringRect + scoring from a rect wrapped according to current wrapping policy) - if (g.NavMoveScoringItems && (item_flags & ImGuiItemFlags_Disabled) == 0 && (window->Flags & ImGuiWindowFlags_NoNavInputs) == 0) + if (g.NavMoveScoringItems && (item_flags & ImGuiItemFlags_Disabled) == 0) { - const bool is_tabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; - if (is_tabbing) + if ((g.NavMoveFlags & ImGuiNavMoveFlags_FocusApi) || (window->Flags & ImGuiWindowFlags_NoNavInputs) == 0) { - NavProcessItemForTabbingRequest(id, item_flags, g.NavMoveFlags); - } - else if (g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) - { - ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; - if (NavScoreItem(result)) - NavApplyItemToResult(result); + const bool is_tabbing = (g.NavMoveFlags & ImGuiNavMoveFlags_IsTabbing) != 0; + if (is_tabbing) + { + NavProcessItemForTabbingRequest(id, item_flags, g.NavMoveFlags); + } + else if (g.NavId != id || (g.NavMoveFlags & ImGuiNavMoveFlags_AllowCurrentNavId)) + { + ImGuiNavItemData* result = (window == g.NavWindow) ? &g.NavMoveResultLocal : &g.NavMoveResultOther; + if (NavScoreItem(result)) + NavApplyItemToResult(result); - // Features like PageUp/PageDown need to maintain a separate score for the visible set of items. - const float VISIBLE_RATIO = 0.70f; - if ((g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) - if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) - if (NavScoreItem(&g.NavMoveResultLocalVisible)) - NavApplyItemToResult(&g.NavMoveResultLocalVisible); + // Features like PageUp/PageDown need to maintain a separate score for the visible set of items. + const float VISIBLE_RATIO = 0.70f; + if ((g.NavMoveFlags & ImGuiNavMoveFlags_AlsoScoreVisibleSet) && window->ClipRect.Overlaps(nav_bb)) + if (ImClamp(nav_bb.Max.y, window->ClipRect.Min.y, window->ClipRect.Max.y) - ImClamp(nav_bb.Min.y, window->ClipRect.Min.y, window->ClipRect.Max.y) >= (nav_bb.Max.y - nav_bb.Min.y) * VISIBLE_RATIO) + if (NavScoreItem(&g.NavMoveResultLocalVisible)) + NavApplyItemToResult(&g.NavMoveResultLocalVisible); + } } } @@ -15001,7 +15010,7 @@ void ImGui::DebugNodeFont(ImFont* font) SetNextItemWidth(GetFontSize() * 8); DragFloat("Font scale", &font->Scale, 0.005f, 0.3f, 2.0f, "%.1f"); SameLine(); MetricsHelpMarker( - "Note than the default embedded font is NOT meant to be scaled.\n\n" + "Note that the default embedded font is NOT meant to be scaled.\n\n" "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " "You may oversample them to get some flexibility with scaling. " "You can also render at multiple sizes and select which one to use at runtime.\n\n" @@ -15460,6 +15469,12 @@ void ImGui::DebugLocateItemResolveWithLastItem() draw_list->AddLine(p1, p2, DEBUG_LOCATE_ITEM_COLOR); } +void ImGui::DebugStartItemPicker() +{ + ImGuiContext& g = *GImGui; + g.DebugItemPickerActive = true; +} + // [DEBUG] Item picker tool - start with DebugStartItemPicker() - useful to visually select an item and break into its call-stack. void ImGui::UpdateDebugToolItemPicker() { @@ -15628,7 +15643,7 @@ void ImGui::ShowIDStackToolWindow(bool* p_open) Checkbox("Ctrl+C: copy path to clipboard", &tool->CopyToClipboardOnCtrlC); SameLine(); TextColored((time_since_copy >= 0.0f && time_since_copy < 0.75f && ImFmod(time_since_copy, 0.25f) < 0.25f * 0.5f) ? ImVec4(1.f, 1.f, 0.3f, 1.f) : ImVec4(), "*COPIED*"); - if (tool->CopyToClipboardOnCtrlC && IsKeyDown(ImGuiMod_Ctrl) && IsKeyPressed(ImGuiKey_C)) + if (tool->CopyToClipboardOnCtrlC && Shortcut(ImGuiMod_Ctrl | ImGuiKey_C, 0, ImGuiInputFlags_RouteGlobal)) { tool->CopyToClipboardLastTime = (float)g.Time; char* p = g.TempBuffer.Data; @@ -15695,6 +15710,7 @@ void ImGui::DebugLog(const char*, ...) {} void ImGui::DebugLogV(const char*, va_list) {} void ImGui::ShowDebugLogWindow(bool*) {} void ImGui::ShowIDStackToolWindow(bool*) {} +void ImGui::DebugStartItemPicker() {} void ImGui::DebugHookIdInfo(ImGuiID, ImGuiDataType, const void*, const void*) {} #endif // #ifndef IMGUI_DISABLE_DEBUG_TOOLS diff --git a/core/deps/imgui/imgui.h b/core/deps/imgui/imgui.h index a7ff9cb91..ecadd5077 100644 --- a/core/deps/imgui/imgui.h +++ b/core/deps/imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.3 +// dear imgui, v1.90.4 // (headers) // Help: @@ -23,8 +23,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.90.3" -#define IMGUI_VERSION_NUM 19030 +#define IMGUI_VERSION "1.90.4" +#define IMGUI_VERSION_NUM 19040 #define IMGUI_HAS_TABLE /* @@ -445,7 +445,7 @@ namespace ImGui IMGUI_API ImVec2 GetFontTexUvWhitePixel(); // get UV coordinate for a while pixel, useful to draw custom shapes via the ImDrawList API IMGUI_API ImU32 GetColorU32(ImGuiCol idx, float alpha_mul = 1.0f); // retrieve given style color with style alpha applied and optional extra alpha multiplier, packed as a 32-bit value suitable for ImDrawList IMGUI_API ImU32 GetColorU32(const ImVec4& col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList - IMGUI_API ImU32 GetColorU32(ImU32 col); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList + IMGUI_API ImU32 GetColorU32(ImU32 col, float alpha_mul = 1.0f); // retrieve given color with style alpha applied, packed as a 32-bit value suitable for ImDrawList IMGUI_API const ImVec4& GetStyleColorVec4(ImGuiCol idx); // retrieve style color as stored in ImGuiStyle structure. use to feed back into PushStyleColor(), otherwise use GetColorU32() to get style color with style alpha baked in. // Layout cursor positioning @@ -967,6 +967,7 @@ namespace ImGui // - Your main debugging friend is the ShowMetricsWindow() function, which is also accessible from Demo->Tools->Metrics Debugger IMGUI_API void DebugTextEncoding(const char* text); IMGUI_API void DebugFlashStyleColor(ImGuiCol idx); + IMGUI_API void DebugStartItemPicker(); IMGUI_API bool DebugCheckVersionAndDataLayout(const char* version_str, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_drawvert, size_t sz_drawidx); // This is called by IMGUI_CHECKVERSION() macro. // Memory Allocators @@ -2766,7 +2767,8 @@ struct ImDrawList IMGUI_API void AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_min, const ImVec2& p_max, const ImVec2& uv_min, const ImVec2& uv_max, ImU32 col, float rounding, ImDrawFlags flags = 0); // Stateful path API, add points then finish with PathFillConvex() or PathStroke() - // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. + // - Important: filled shapes must always use clockwise winding order! The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. + // so e.g. 'PathArcTo(center, radius, PI * -0.5f, PI)' is ok, whereas 'PathArcTo(center, radius, PI, PI * -0.5f)' won't have correct anti-aliasing when followed by PathFillConvex(). inline void PathClear() { _Path.Size = 0; } inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } diff --git a/core/deps/imgui/imgui_demo.cpp b/core/deps/imgui/imgui_demo.cpp index bbed74244..707c14d38 100644 --- a/core/deps/imgui/imgui_demo.cpp +++ b/core/deps/imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.3 +// dear imgui, v1.90.4 // (demo code) // Help: @@ -401,6 +401,12 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::MenuItem("Debug Log", NULL, &show_tool_debug_log, has_debug_tools); ImGui::MenuItem("ID Stack Tool", NULL, &show_tool_id_stack_tool, has_debug_tools); ImGui::MenuItem("Style Editor", NULL, &show_tool_style_editor); + bool is_debugger_present = ImGui::GetIO().ConfigDebugIsDebuggerPresent; + if (ImGui::MenuItem("Item Picker", NULL, false, has_debug_tools && is_debugger_present)) + ImGui::DebugStartItemPicker(); + if (!is_debugger_present) + ImGui::SetItemTooltip("Requires io.ConfigDebugIsDebuggerPresent=true to be set.\n\nWe otherwise disable the menu option to avoid casual users crashing the application.\n\nYou can however always access the Item Picker in Metrics->Tools."); + ImGui::Separator(); ImGui::MenuItem("About Dear ImGui", NULL, &show_tool_about); ImGui::EndMenu(); } @@ -5307,23 +5313,26 @@ static void ShowDemoWindowTables() const int rows_count = 12; static ImGuiTableFlags table_flags = ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollX | ImGuiTableFlags_ScrollY | ImGuiTableFlags_BordersOuter | ImGuiTableFlags_BordersInnerH | ImGuiTableFlags_Hideable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_HighlightHoveredColumn; + static ImGuiTableColumnFlags column_flags = ImGuiTableColumnFlags_AngledHeader | ImGuiTableColumnFlags_WidthFixed; static bool bools[columns_count * rows_count] = {}; // Dummy storage selection storage static int frozen_cols = 1; static int frozen_rows = 2; ImGui::CheckboxFlags("_ScrollX", &table_flags, ImGuiTableFlags_ScrollX); ImGui::CheckboxFlags("_ScrollY", &table_flags, ImGuiTableFlags_ScrollY); + ImGui::CheckboxFlags("_Resizable", &table_flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("_NoBordersInBody", &table_flags, ImGuiTableFlags_NoBordersInBody); ImGui::CheckboxFlags("_HighlightHoveredColumn", &table_flags, ImGuiTableFlags_HighlightHoveredColumn); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); ImGui::SliderInt("Frozen columns", &frozen_cols, 0, 2); ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); ImGui::SliderInt("Frozen rows", &frozen_rows, 0, 2); + ImGui::CheckboxFlags("Disable header contributing to column width", &column_flags, ImGuiTableColumnFlags_NoHeaderWidth); if (ImGui::BeginTable("table_angled_headers", columns_count, table_flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 12))) { ImGui::TableSetupColumn(column_names[0], ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder); for (int n = 1; n < columns_count; n++) - ImGui::TableSetupColumn(column_names[n], ImGuiTableColumnFlags_AngledHeader | ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn(column_names[n], column_flags); ImGui::TableSetupScrollFreeze(frozen_cols, frozen_rows); ImGui::TableAngledHeadersRow(); // Draw angled headers for all columns with the ImGuiTableColumnFlags_AngledHeader flag. @@ -8027,6 +8036,9 @@ static void ShowExampleAppCustomRendering(bool* p_open) const float rounding = sz / 5.0f; const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; + const ImVec2 cp3[3] = { ImVec2(0.0f, sz * 0.6f), ImVec2(sz * 0.5f, -sz * 0.4f), ImVec2(sz, sz) }; // Control points for curves + const ImVec2 cp4[4] = { ImVec2(0.0f, 0.0f), ImVec2(sz * 1.3f, sz * 0.3f), ImVec2(sz - sz * 1.3f, sz - sz * 0.3f), ImVec2(sz, sz) }; + float x = p.x + 4.0f; float y = p.y + 4.0f; for (int n = 0; n < 2; n++) @@ -8045,17 +8057,23 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line + // Path + draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); + draw_list->PathStroke(col, ImDrawFlags_None, th); + x += sz + spacing; + // Quadratic Bezier Curve (3 control points) - ImVec2 cp3[3] = { ImVec2(x, y + sz * 0.6f), ImVec2(x + sz * 0.5f, y - sz * 0.4f), ImVec2(x + sz, y + sz) }; - draw_list->AddBezierQuadratic(cp3[0], cp3[1], cp3[2], col, th, curve_segments); x += sz + spacing; + draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments); + x += sz + spacing; // Cubic Bezier Curve (4 control points) - ImVec2 cp4[4] = { ImVec2(x, y), ImVec2(x + sz * 1.3f, y + sz * 0.3f), ImVec2(x + sz - sz * 1.3f, y + sz - sz * 0.3f), ImVec2(x + sz, y + sz) }; - draw_list->AddBezierCubic(cp4[0], cp4[1], cp4[2], cp4[3], col, th, curve_segments); + draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments); x = p.x + 4; y += sz + spacing; } + + // Filled shapes draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngon_sides); x += sz + spacing; // N-gon draw_list->AddCircleFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circle_segments); x += sz + spacing; // Circle draw_list->AddEllipseFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, sz * 0.3f, col, -0.3f, circle_segments); x += sz + spacing;// Ellipse @@ -8067,9 +8085,27 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) + + // Path + draw_list->PathArcTo(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, 3.141592f * -0.5f, 3.141592f); + draw_list->PathFillConvex(col); + x += sz + spacing; + + // Quadratic Bezier Curve (3 control points) + draw_list->PathLineTo(ImVec2(x + cp3[0].x, y + cp3[0].y)); + draw_list->PathBezierQuadraticCurveTo(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), curve_segments); + draw_list->PathFillConvex(col); + x += sz + spacing; + + // Cubic Bezier Curve (4 control points): this is concave so not drawing it yet + //draw_list->PathLineTo(ImVec2(x + cp4[0].x, y + cp4[0].y)); + //draw_list->PathBezierCubicCurveTo(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), curve_segments); + //draw_list->PathFillConvex(col); + //x += sz + spacing; + draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); - ImGui::Dummy(ImVec2((sz + spacing) * 11.2f, (sz + spacing) * 3.0f)); + ImGui::Dummy(ImVec2((sz + spacing) * 12.2f, (sz + spacing) * 3.0f)); ImGui::PopItemWidth(); ImGui::EndTabItem(); } diff --git a/core/deps/imgui/imgui_draw.cpp b/core/deps/imgui/imgui_draw.cpp index ca1fe7d0f..1319a6e1d 100644 --- a/core/deps/imgui/imgui_draw.cpp +++ b/core/deps/imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.3 +// dear imgui, v1.90.4 // (drawing and font code) /* @@ -3997,8 +3997,8 @@ void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, Im } else { - draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b, 3); // BL - draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e, 3); // TR + draw_list->PathArcTo(ImVec2(x0, p1.y - rounding), rounding, IM_PI - arc0_e, IM_PI - arc0_b); // BL + draw_list->PathArcTo(ImVec2(x0, p0.y + rounding), rounding, IM_PI + arc0_b, IM_PI + arc0_e); // TR } if (p1.x > rect.Min.x + rounding) { @@ -4017,8 +4017,8 @@ void ImGui::RenderRectFilledRangeH(ImDrawList* draw_list, const ImRect& rect, Im } else { - draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b, 3); // TR - draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e, 3); // BR + draw_list->PathArcTo(ImVec2(x1, p0.y + rounding), rounding, -arc1_e, -arc1_b); // TR + draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e); // BR } } draw_list->PathFillConvex(col); diff --git a/core/deps/imgui/imgui_internal.h b/core/deps/imgui/imgui_internal.h index 41a792de8..eee791d04 100644 --- a/core/deps/imgui/imgui_internal.h +++ b/core/deps/imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.3 +// dear imgui, v1.90.4 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -2481,6 +2481,7 @@ struct IMGUI_API ImGuiWindowTempData int CurrentTableIdx; // Current table index (into g.Tables) ImGuiLayoutType LayoutType; ImGuiLayoutType ParentLayoutType; // Layout type of parent window at the time of Begin() + ImU32 ModalDimBgColor; // Local parameters stacks // We store the current settings outside of the vectors to increase memory locality (reduce cache misses). The vectors are rarely modified. Also it allows us to not heap allocate for short-lived windows which are not using those settings. @@ -2918,7 +2919,7 @@ struct IMGUI_API ImGuiTableTempData { int TableIndex; // Index in g.Tables.Buf[] pool float LastTimeActive; // Last timestamp this structure was used - float AngledheadersExtraWidth; // Used in EndTable() + float AngledHeadersExtraWidth; // Used in EndTable() ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() ImDrawListSplitter DrawSplitter; @@ -3302,7 +3303,7 @@ namespace ImGui IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); IMGUI_API void TablePushBackgroundChannel(); IMGUI_API void TablePopBackgroundChannel(); - IMGUI_API void TableAngledHeadersRowEx(float angle, float label_width = 0.0f); + IMGUI_API void TableAngledHeadersRowEx(float angle, float max_label_width = 0.0f); // Tables: Internals inline ImGuiTable* GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; } @@ -3485,7 +3486,6 @@ namespace ImGui IMGUI_API void DebugBreakClearData(); IMGUI_API bool DebugBreakButton(const char* label, const char* description_of_location); IMGUI_API void DebugBreakButtonTooltip(bool keyboard_only, const char* description_of_location); - inline void DebugStartItemPicker() { ImGuiContext& g = *GImGui; g.DebugItemPickerActive = true; } IMGUI_API void ShowFontAtlas(ImFontAtlas* atlas); IMGUI_API void DebugHookIdInfo(ImGuiID id, ImGuiDataType data_type, const void* data_id, const void* data_id_end); IMGUI_API void DebugNodeColumns(ImGuiOldColumns* columns); diff --git a/core/deps/imgui/imgui_tables.cpp b/core/deps/imgui/imgui_tables.cpp index 36c4c95b7..260df1a92 100644 --- a/core/deps/imgui/imgui_tables.cpp +++ b/core/deps/imgui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.3 +// dear imgui, v1.90.4 // (tables and columns code) /* @@ -498,7 +498,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->DeclColumnsCount = table->AngledHeadersCount = 0; if (previous_frame_active + 1 < g.FrameCount) table->IsActiveIdInTable = false; - temp_data->AngledheadersExtraWidth = 0.0f; + temp_data->AngledHeadersExtraWidth = 0.0f; // Using opaque colors facilitate overlapping lines of the grid, otherwise we'd need to improve TableDrawBorders() table->BorderColorStrong = GetColorU32(ImGuiCol_TableBorderStrong); @@ -1344,7 +1344,7 @@ void ImGui::EndTable() max_pos_x = ImMax(max_pos_x, table->Columns[table->RightMostEnabledColumn].WorkMaxX + table->CellPaddingX + table->OuterPaddingX - outer_padding_for_border); if (table->ResizedColumn != -1) max_pos_x = ImMax(max_pos_x, table->ResizeLockMinContentsX2); - table->InnerWindow->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledheadersExtraWidth; + table->InnerWindow->DC.CursorMaxPos.x = max_pos_x + table->TempData->AngledHeadersExtraWidth; } // Pop clipping rect @@ -1462,7 +1462,7 @@ void ImGui::EndTable() } else if (temp_data->UserOuterSize.x <= 0.0f) { - const float decoration_size = table->TempData->AngledheadersExtraWidth + ((table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f); + const float decoration_size = table->TempData->AngledHeadersExtraWidth + ((table->Flags & ImGuiTableFlags_ScrollX) ? inner_window->ScrollbarSizes.x : 0.0f); outer_window->DC.IdealMaxPos.x = ImMax(outer_window->DC.IdealMaxPos.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth + decoration_size - temp_data->UserOuterSize.x); outer_window->DC.CursorMaxPos.x = ImMax(backup_outer_max_pos.x, ImMin(table->OuterRect.Max.x, table->OuterRect.Min.x + table->ColumnsAutoFitWidth)); } @@ -1567,6 +1567,7 @@ void ImGui::TableSetupColumn(const char* label, ImGuiTableColumnFlags flags, flo } // Store name (append with zero-terminator in contiguous buffer) + // FIXME: If we recorded the number of \n in names we could compute header row height column->NameOffset = -1; if (label != NULL && label[0] != 0) { @@ -2154,6 +2155,8 @@ void ImGui::TableEndCell(ImGuiTable* table) // - TableSetColumnWidthAutoAll() [Internal] // - TableUpdateColumnsWeightFromWidth() [Internal] //------------------------------------------------------------------------- +// Note that actual columns widths are computed in TableUpdateLayout(). +//------------------------------------------------------------------------- // Maximum column content width given current layout. Use column->MinX so this value on a per-column basis. float ImGui::TableGetMaxColumnWidth(const ImGuiTable* table, int column_n) @@ -2927,6 +2930,7 @@ void ImGui::TableSortSpecsBuild(ImGuiTable* table) // [SECTION] Tables: Headers //------------------------------------------------------------------------- // - TableGetHeaderRowHeight() [Internal] +// - TableGetHeaderAngledMaxLabelWidth() [Internal] // - TableHeadersRow() // - TableHeader() // - TableAngledHeadersRow() @@ -2958,7 +2962,7 @@ float ImGui::TableGetHeaderAngledMaxLabelWidth() if (IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n)) if (table->Columns[column_n].Flags & ImGuiTableColumnFlags_AngledHeader) width = ImMax(width, CalcTextSize(TableGetColumnName(table, column_n), NULL, true).x); - return width + g.Style.CellPadding.x * 2.0f; + return width + g.Style.CellPadding.y * 2.0f; // Swap padding } // [Public] This is a helper to output TableHeader() calls based on the column names declared in TableSetupColumn(). @@ -3180,25 +3184,25 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) // Calculate our base metrics and set angled headers data _before_ the first call to TableNextRow() // FIXME-STYLE: Would it be better for user to submit 'max_label_width' or 'row_height' ? One can be derived from the other. - const float header_height = table->RowCellPaddingY * 2.0f + g.FontSize; + const float header_height = g.FontSize + g.Style.CellPadding.x * 2.0f; const float row_height = ImFabs(ImRotate(ImVec2(max_label_width, flip_label ? +header_height : -header_height), cos_a, sin_a).y); - const ImVec2 header_angled_vector = unit_right * (row_height / -sin_a); table->AngledHeadersHeight = row_height; table->AngledHeadersSlope = (sin_a != 0.0f) ? (cos_a / sin_a) : 0.0f; + const ImVec2 header_angled_vector = unit_right * (row_height / -sin_a); // vector from bottom-left to top-left, and from bottom-right to top-right // Declare row, override and draw our own background TableNextRow(ImGuiTableRowFlags_Headers, row_height); TableNextColumn(); + const ImRect row_r(table->WorkRect.Min.x, table->BgClipRect.Min.y, table->WorkRect.Max.x, table->RowPosY2); table->DrawSplitter->SetCurrentChannel(draw_list, TABLE_DRAW_CHANNEL_BG0); float clip_rect_min_x = table->BgClipRect.Min.x; if (table->FreezeColumnsCount > 0) clip_rect_min_x = ImMax(clip_rect_min_x, table->Columns[table->FreezeColumnsCount - 1].MaxX); TableSetBgColor(ImGuiTableBgTarget_RowBg0, 0); // Cancel PushClipRect(table->BgClipRect.Min, table->BgClipRect.Max, false); // Span all columns - draw_list->AddRectFilled(table->BgClipRect.Min, table->BgClipRect.Max, GetColorU32(ImGuiCol_TableHeaderBg, 0.25f)); // FIXME-STYLE: Change row background with an arbitrary color. + draw_list->AddRectFilled(ImVec2(table->BgClipRect.Min.x, row_r.Min.y), ImVec2(table->BgClipRect.Max.x, row_r.Max.y), GetColorU32(ImGuiCol_TableHeaderBg, 0.25f)); // FIXME-STYLE: Change row background with an arbitrary color. PushClipRect(ImVec2(clip_rect_min_x, table->BgClipRect.Min.y), table->BgClipRect.Max, true); // Span all columns - const ImRect row_r(table->WorkRect.Min.x, table->BgClipRect.Min.y, table->WorkRect.Max.x, window->DC.CursorPos.y + row_height); const ImGuiID row_id = GetID("##AngledHeaders"); ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); @@ -3209,7 +3213,9 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) if (table_instance->HoveredRowLast == 0 && table->HoveredColumnBorder == -1 && (g.ActiveId == 0 || g.ActiveId == row_id || (table->IsActiveIdInTable || g.DragDropActive))) highlight_column_n = table->HoveredColumnBody; + // Draw background and labels in first pass, then all borders. float max_x = 0.0f; + ImVec2 padding = g.Style.CellPadding; // We will always use swapped component for (int pass = 0; pass < 2; pass++) for (int order_n = 0; order_n < table->ColumnsCount; order_n++) { @@ -3231,25 +3237,45 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], GetColorU32(ImGuiCol_TableHeaderBg)); if (column_n == highlight_column_n) draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], GetColorU32(ImGuiCol_Header)); // Highlight on hover - //draw_list->AddQuad(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], GetColorU32(ImGuiCol_TableBorderLight), 1.0f); max_x = ImMax(max_x, bg_shape[3].x); - // Draw label (first draw at an offset where RenderTextXXX() function won't meddle with applying current ClipRect, then transform to final offset) - // FIXME: May be worth tidying up all those operations to make them easier to understand. + // Draw label + // - First draw at an offset where RenderTextXXX() function won't meddle with applying current ClipRect, then transform to final offset. + // - Handle multiple lines manually, as we want each lines to follow on the horizontal border, rather than see a whole block rotated. const char* label_name = TableGetColumnName(table, column_n); - const float clip_width = max_label_width - (sin_a * table->RowCellPaddingY); - ImRect label_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width + (flip_label ? 0.0f : table->CellPaddingX), header_height + table->RowCellPaddingY)); - ImVec2 label_size = CalcTextSize(label_name, NULL, true); - ImVec2 label_off = ImVec2(flip_label ? ImMax(0.0f, max_label_width - label_size.x - table->CellPaddingX) : table->CellPaddingX, table->RowCellPaddingY); - int vtx_idx_begin = draw_list->_VtxCurrentIdx; - RenderTextEllipsis(draw_list, label_r.Min + label_off, label_r.Max, label_r.Max.x, label_r.Max.x, label_name, NULL, &label_size); - //if (g.IO.KeyShift) { draw_list->AddRect(label_r.Min, label_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 2.0f); } - int vtx_idx_end = draw_list->_VtxCurrentIdx; + const char* label_name_end = FindRenderedTextEnd(label_name); + const float line_off_step_x = g.FontSize / -sin_a; + float line_off_curr_x = 0.0f; + while (label_name < label_name_end) + { + const char* label_name_eol = strchr(label_name, '\n'); + if (label_name_eol == NULL) + label_name_eol = label_name_end; - // Rotate and offset label - ImVec2 pivot_in = label_r.GetBL(); - ImVec2 pivot_out = ImVec2(column->WorkMinX, row_r.Max.y) + (flip_label ? (unit_right * clip_width) : ImVec2(header_height, 0.0f)); - ShadeVertsTransformPos(draw_list, vtx_idx_begin, vtx_idx_end, pivot_in, label_cos_a, label_sin_a, pivot_out); // Rotate and offset + // FIXME: Individual line clipping for right-most column is broken for negative angles. + ImVec2 label_size = CalcTextSize(label_name, label_name_eol); + float clip_width = max_label_width - padding.y; // Using padding.y*2.0f would be symetrical but hide more text. + float clip_height = ImMin(label_size.y, column->ClipRect.Max.x - column->WorkMinX - line_off_curr_x); + ImRect clip_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width, clip_height)); + int vtx_idx_begin = draw_list->_VtxCurrentIdx; + RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, clip_r.Max.x, label_name, label_name_eol, &label_size); + int vtx_idx_end = draw_list->_VtxCurrentIdx; + + // Rotate and offset label + ImVec2 pivot_in = ImVec2(window->ClipRect.Min.x, window->ClipRect.Min.y + label_size.y); + ImVec2 pivot_out = ImVec2(column->WorkMinX, row_r.Max.y); + line_off_curr_x += line_off_step_x; + pivot_out += unit_right * padding.y; + if (flip_label) + pivot_out += unit_right * (clip_width - ImMax(0.0f, clip_width - label_size.x)); + pivot_out.x += flip_label ? line_off_curr_x - line_off_step_x : line_off_curr_x; + ShadeVertsTransformPos(draw_list, vtx_idx_begin, vtx_idx_end, pivot_in, label_cos_a, label_sin_a, pivot_out); // Rotate and offset + //if (g.IO.KeyShift) { ImDrawList* fg_dl = GetForegroundDrawList(); vtx_idx_begin = fg_dl->_VtxCurrentIdx; fg_dl->AddRect(clip_r.Min, clip_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 2.0f); ShadeVertsTransformPos(fg_dl, vtx_idx_begin, fg_dl->_VtxCurrentIdx, pivot_in, label_cos_a, label_sin_a, pivot_out); } + + // Register header width + column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX + ImCeil(line_off_curr_x); + label_name = label_name_eol + 1; + } } if (pass == 1) { @@ -3259,7 +3285,7 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) } PopClipRect(); PopClipRect(); - table->TempData->AngledheadersExtraWidth = ImMax(0.0f, max_x - table->Columns[table->RightMostEnabledColumn].MaxX); + table->TempData->AngledHeadersExtraWidth = ImMax(0.0f, max_x - table->Columns[table->RightMostEnabledColumn].MaxX); } //------------------------------------------------------------------------- diff --git a/core/deps/imgui/imgui_widgets.cpp b/core/deps/imgui/imgui_widgets.cpp index b5c5d2750..5ce24b417 100644 --- a/core/deps/imgui/imgui_widgets.cpp +++ b/core/deps/imgui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.3 +// dear imgui, v1.90.4 // (widgets code) /* From 3c8769cb5cf0d083bfdd2ec1588864501da16981 Mon Sep 17 00:00:00 2001 From: scribam Date: Sat, 24 Feb 2024 13:42:22 +0100 Subject: [PATCH 19/86] deps: sync imgui_impl_vulkan.cpp with upstream --- core/deps/imgui/backends/imgui_impl_vulkan.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/core/deps/imgui/backends/imgui_impl_vulkan.cpp b/core/deps/imgui/backends/imgui_impl_vulkan.cpp index 62acf10b0..a8962ecd2 100644 --- a/core/deps/imgui/backends/imgui_impl_vulkan.cpp +++ b/core/deps/imgui/backends/imgui_impl_vulkan.cpp @@ -502,11 +502,6 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Create or resize the vertex/index buffers size_t vertex_size = AlignBufferSize(draw_data->TotalVtxCount * sizeof(ImDrawVert), bd->BufferMemoryAlignment); size_t index_size = AlignBufferSize(draw_data->TotalIdxCount * sizeof(ImDrawIdx), bd->BufferMemoryAlignment); - if (vertex_size != 0) - vertex_size = ((vertex_size - 1) / bd->BufferMemoryAlignment + 1) * bd->BufferMemoryAlignment; - if (index_size != 0) - index_size = ((index_size - 1) / bd->BufferMemoryAlignment + 1) * bd->BufferMemoryAlignment; - if (rb->VertexBuffer == VK_NULL_HANDLE || rb->VertexBufferSize < vertex_size) CreateOrResizeBuffer(rb->VertexBuffer, rb->VertexBufferMemory, rb->VertexBufferSize, vertex_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); if (rb->IndexBuffer == VK_NULL_HANDLE || rb->IndexBufferSize < index_size) From 2dde1d5e7bb195685529f320a545934cc5a1a29b Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 25 Feb 2024 14:37:27 +0100 Subject: [PATCH 20/86] sh4: fix P4 region mapping and on chip ram addressing P4 region is normally mapped outside of the sq and system registers areas (E4-EF, F8-FE). Fixes NHL 2K2 missing helmet and jersey textures. Issue #1416 Fixes invalid geometry in worldkicks series (using interpreter or 4 GB vmem space) Fix On Chip RAM addressing: bit 13 or 25 is used to select the bank. --- core/hw/holly/sb_mem.cpp | 2 -- core/hw/sh4/sh4_mem.cpp | 21 +++++++-------- core/hw/sh4/sh4_mmr.cpp | 58 +++++++++++++++++----------------------- core/hw/sh4/sh4_mmr.h | 20 ++++++-------- 4 files changed, 43 insertions(+), 58 deletions(-) diff --git a/core/hw/holly/sb_mem.cpp b/core/hw/holly/sb_mem.cpp index db37a99e1..1bfc6880c 100644 --- a/core/hw/holly/sb_mem.cpp +++ b/core/hw/holly/sb_mem.cpp @@ -326,8 +326,6 @@ void map_area0_init() } void map_area0(u32 base) { - verify(base<0xE0); - addrspace::mapHandler(area0_handler, 0x00 | base, 0x01 | base); addrspace::mapHandler(area0_mirror_handler, 0x02 | base, 0x03 | base); diff --git a/core/hw/sh4/sh4_mem.cpp b/core/hw/sh4/sh4_mem.cpp index d44d309f8..f7ca1f1c5 100644 --- a/core/hw/sh4/sh4_mem.cpp +++ b/core/hw/sh4/sh4_mem.cpp @@ -155,22 +155,21 @@ void mem_map_default() map_area4_init(); map_area5_init(); map_area6_init(); - map_area7_init(); - // 00-0C: 7 times the normal memmap mirrors - for (int i = 0; i < 7; i++) + // 00-E0: 8 times the normal memmap mirrors + for (int i = 0; i < 8; i++) { - map_area0(i << 5); //Bios,Flahsrom,i/f regs,Ext. Device,Sound Ram - map_area1(i << 5); //VRAM + map_area0(i << 5); // Bios,Flahsrom,i/f regs,Ext. Device,Sound Ram + map_area1(i << 5); // VRAM elan::vmem_map(i << 5); // Naomi2 Elan - map_area3(i << 5); //RAM - map_area4(i << 5); //TA - map_area5(i << 5); //Ext. Device - map_area6(i << 5); //Unassigned - map_area7(i << 5); //Sh4 Regs + map_area3(i << 5); // RAM + map_area4(i << 5); // TA + map_area5(i << 5); // Ext. Device + map_area6(i << 5); // Unassigned } + map_area7(); // On Chip RAM - // E0: p4 region + // E0-FF: P4 region map_p4(); } void mem_Init() diff --git a/core/hw/sh4/sh4_mmr.cpp b/core/hw/sh4/sh4_mmr.cpp index 8cd75bd65..5d1579830 100644 --- a/core/hw/sh4/sh4_mmr.cpp +++ b/core/hw/sh4/sh4_mmr.cpp @@ -17,9 +17,7 @@ #include #include -//64bytes of sq // now on context ~ - -static std::array OnChipRAM; +static std::array OnChipRAM; // 8 KB //All registers are 4 byte aligned @@ -390,7 +388,8 @@ void DYNACALL WriteMem_P4(u32 addr,T data) template T DYNACALL ReadMem_p4mmr(u32 addr) { - DEBUG_LOG(SH4, "read %s", regName(addr)); + if ((addr & 0x1fffffff) != TMU_TCNT0_addr) + DEBUG_LOG(SH4, "read %s", regName(addr)); /* if (likely(addr == 0xffd80024)) @@ -558,11 +557,16 @@ void DYNACALL WriteMem_p4mmr(u32 addr, T data) //*********** //On Chip Ram //*********** + +inline static u32 onChipRamOffset(u32 addr) { + return ((addr >> (CCN_CCR.OIX == 1 ? 13 : 1)) & 0x1000) | (addr & 0xfff); +} + template T DYNACALL ReadMem_area7_OCR(u32 addr) { if (CCN_CCR.ORA == 1) - return *(T *)&OnChipRAM[addr & OnChipRAM_MASK]; + return *(T *)&OnChipRAM[onChipRamOffset(addr)]; INFO_LOG(SH4, "On Chip Ram Read, but OCR is disabled. addr %x", addr); return 0; @@ -572,7 +576,7 @@ template void DYNACALL WriteMem_area7_OCR(u32 addr, T data) { if (CCN_CCR.ORA == 1) - *(T *)&OnChipRAM[addr & OnChipRAM_MASK] = data; + *(T *)&OnChipRAM[onChipRamOffset(addr)] = data; else INFO_LOG(SH4, "On Chip Ram Write, but OCR is disabled. addr %x", addr); } @@ -630,40 +634,28 @@ void sh4_mmr_term() bsc.term(); } -// AREA 7--Sh4 Regs -static addrspace::handler p4mmr_handler; -static addrspace::handler area7_ocr_handler; - -void map_area7_init() -{ - p4mmr_handler = addrspaceRegisterHandlerTemplate(ReadMem_p4mmr, WriteMem_p4mmr); - area7_ocr_handler = addrspaceRegisterHandlerTemplate(ReadMem_area7_OCR, WriteMem_area7_OCR); -} - -void map_area7(u32 base) +// AREA 7 +void map_area7() { // on-chip RAM: 7C000000-7FFFFFFF - if (base == 0x60) - addrspace::mapHandler(area7_ocr_handler, 0x7C, 0x7F); + addrspace::handler area7_ocr_handler = addrspaceRegisterHandlerTemplate(ReadMem_area7_OCR, WriteMem_area7_OCR); + addrspace::mapHandler(area7_ocr_handler, 0x7C, 0x7F); } -//P4 +// P4 void map_p4() { - //P4 Region : - addrspace::handler p4_handler = addrspaceRegisterHandlerTemplate(ReadMem_P4, WriteMem_P4); - - //register this before mmr and SQ so they overwrite it and handle em - //default P4 handler - //0xE0000000-0xFFFFFFFF - addrspace::mapHandler(p4_handler, 0xE0, 0xFF); - - //Store Queues -- Write only 32bit - addrspace::mapBlock(sq_both, 0xE0, 0xE0, 63); - addrspace::mapBlock(sq_both, 0xE1, 0xE1, 63); - addrspace::mapBlock(sq_both, 0xE2, 0xE2, 63); - addrspace::mapBlock(sq_both, 0xE3, 0xE3, 63); + // Store Queues -- Write only 32bit + addrspace::mapBlock(p_sh4rcb->sq_buffer, 0xE0, 0xE0, 63); + addrspace::mapBlock(p_sh4rcb->sq_buffer, 0xE1, 0xE1, 63); + addrspace::mapBlock(p_sh4rcb->sq_buffer, 0xE2, 0xE2, 63); + addrspace::mapBlock(p_sh4rcb->sq_buffer, 0xE3, 0xE3, 63); + // sh4 IC, OC and TLB arrays + addrspace::handler p4arrays_handler = addrspaceRegisterHandlerTemplate(ReadMem_P4, WriteMem_P4); + addrspace::mapHandler(p4arrays_handler, 0xF0, 0xF7); + // sh4 system registers + addrspace::handler p4mmr_handler = addrspaceRegisterHandlerTemplate(ReadMem_p4mmr, WriteMem_p4mmr); addrspace::mapHandler(p4mmr_handler, 0xFF, 0xFF); } diff --git a/core/hw/sh4/sh4_mmr.h b/core/hw/sh4/sh4_mmr.h index cf9d782e7..04bd7859a 100644 --- a/core/hw/sh4/sh4_mmr.h +++ b/core/hw/sh4/sh4_mmr.h @@ -6,13 +6,9 @@ #include "modules/dmac.h" //For mem mapping -void map_area7_init(); -void map_area7(u32 base); +void map_area7(); void map_p4(); -#define OnChipRAM_SIZE (0x2000) -#define OnChipRAM_MASK (OnChipRAM_SIZE-1) - #define sq_both (sh4rcb.sq_buffer) void sh4_mmr_init(); @@ -1086,17 +1082,17 @@ union DMAC_DMAOR_type { struct { - u32 DME : 1; - u32 NMIF : 1; - u32 AE : 1; + u32 DME : 1; // DMAC master enable + u32 NMIF : 1; // NMI flag + u32 AE : 1; // address error flag u32 : 1; - u32 COD : 1; + u32 COD : 1; // check overrun for DREQ u32 : 3; - u32 PR0 : 1; - u32 PR1 : 1; + u32 PR0 : 1; // priority mode: + u32 PR1 : 1; // PR0: 0, PR1: 1 => CH2 > CH0 > CH1 > CH3 u32 : 5; - u32 DDT : 1; + u32 DDT : 1; // 0: normal DMA mode, 1: on-demand data transfer mode u32 : 16; }; From 4f1512d36923ee73674a7d0361635336f0af8bed Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 25 Feb 2024 14:40:37 +0100 Subject: [PATCH 21/86] bump depth scale for NHL 2K2 Fixes helmet rendering in team selection menu Issue #1416 --- core/emulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/emulator.cpp b/core/emulator.cpp index 27beaadf9..5d7fadb30 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -149,7 +149,7 @@ static void loadSpecialSettings() if (prod_id == "MK-51182") { INFO_LOG(BOOT, "Enabling Extra depth scaling for game %s", prod_id.c_str()); - config::ExtraDepthScale.override(1000000.f); // Mali needs 1M, 10K is enough for others + config::ExtraDepthScale.override(1e8f); } // Re-Volt (US, EU, JP) else if (prod_id == "T-8109N" || prod_id == "T8107D 50" || prod_id == "T-8101M") From ff91d1101a224ca992466e4019d32edf2548ca32 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 25 Feb 2024 15:59:22 +0100 Subject: [PATCH 22/86] isofs: fix long directory listing Directory entries can't cross a sector boundary, so reading must continue on the next one. Add list() method --- core/imgread/isofs.cpp | 102 +++++++++++++++++++++++++++++------------ core/imgread/isofs.h | 16 ++++--- 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/core/imgread/isofs.cpp b/core/imgread/isofs.cpp index d22ac3aff..31119dba2 100644 --- a/core/imgread/isofs.cpp +++ b/core/imgread/isofs.cpp @@ -61,41 +61,69 @@ IsoFs::Directory *IsoFs::getRoot() return root; } +void IsoFs::Directory::reset() +{ + index = 0; + if (data.empty() && len != 0) + { + data.resize(len); + fs->disc->ReadSectors(startFad, len / 2048, data.data(), 2048); + } +} + +IsoFs::Entry *IsoFs::Directory::nextEntry() +{ + if (index >= data.size()) + return nullptr; + const iso9660_dir_t *dir = (const iso9660_dir_t *)&data[index]; + if (dir->length == 0) + { + if ((index & 2047) == 0) + return nullptr; + index = ((index + 2047) / 2048) * 2048; + if (index >= data.size()) + return nullptr; + dir = (const iso9660_dir_t *)&data[index]; + if (dir->length == 0) + return nullptr; + } + std::string name(dir->filename.str + 1, dir->filename.str[0]); + + u32 startFad = decode_iso733(dir->extent) + 150; + u32 len = decode_iso733(dir->size); + Entry *entry; + if ((dir->file_flags & ISO_DIRECTORY) == 0) + { + File *file = new File(fs); + entry = file; + } + else + { + Directory *directory = new Directory(fs); + len = ((len + 2047) / 2048) * 2048; + entry = directory; + } + entry->startFad = startFad; + entry->len = len; + entry->name = name; + index += dir->length; + + return entry; +} + IsoFs::Entry *IsoFs::Directory::getEntry(const std::string& name) { std::string isoname = name + ';'; - for (u32 i = 0; i < data.size(); ) + reset(); + while (true) { - const iso9660_dir_t *dir = (const iso9660_dir_t *)&data[i]; - if (dir->length == 0) - break; - - if ((u8)dir->filename.str[0] > isoname.size() - && memcmp(dir->filename.str + 1, isoname.c_str(), isoname.size()) == 0) - { - DEBUG_LOG(GDROM, "Found %s at offset %X", name.c_str(), i); - u32 startFad = decode_iso733(dir->extent) + 150; - u32 len = decode_iso733(dir->size); - if ((dir->file_flags & ISO_DIRECTORY) == 0) - { - File *file = new File(fs); - file->startFad = startFad; - file->len = len; - - return file; - } - else - { - Directory *directory = new Directory(fs); - directory->data.resize(len); - fs->disc->ReadSectors(startFad, len / 2048, directory->data.data(), 2048); - - return directory; - } - } - i += dir->length; + Entry *entry = nextEntry(); + if (entry == nullptr) + return nullptr; + if (entry->getName().substr(0, isoname.size()) == isoname) + return entry; + delete entry; } - return nullptr; } u32 IsoFs::File::read(u8 *buf, u32 size, u32 offset) const @@ -112,3 +140,17 @@ u32 IsoFs::File::read(u8 *buf, u32 size, u32 offset) const } return sectors * 2048 + size; } + +std::vector IsoFs::Directory::list() +{ + std::vector v; + reset(); + while (true) + { + Entry *entry = nextEntry(); + if (entry == nullptr) + break; + v.push_back(entry); + } + return v; +} diff --git a/core/imgread/isofs.h b/core/imgread/isofs.h index 4d2944900..839961872 100644 --- a/core/imgread/isofs.h +++ b/core/imgread/isofs.h @@ -27,24 +27,33 @@ public: public: virtual bool isDirectory() const = 0; virtual ~Entry() = default; + const std::string& getName() const { return name; } protected: Entry(IsoFs *fs) : fs(fs) {} IsoFs *fs; + std::string name; + u32 startFad = 0; + u32 len = 0; + + friend class IsoFs; }; class Directory final : public Entry { public: bool isDirectory() const override { return true; } - Entry *getEntry(const std::string& name); + std::vector list(); private: Directory(IsoFs *fs) : Entry(fs) {} + void reset(); + Entry *nextEntry(); std::vector data; + u32 index = 0; friend class IsoFs; }; @@ -53,17 +62,12 @@ public: { public: bool isDirectory() const override { return false; } - u32 getSize() const { return len; } - u32 read(u8 *buf, u32 size, u32 offset = 0) const; private: File(IsoFs *fs) : Entry(fs) {} - u32 startFad = 0; - u32 len = 0; - friend class IsoFs; }; From f629c352e175a2cba7cb192033db22db2b52f662 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 26 Feb 2024 11:27:03 +0100 Subject: [PATCH 23/86] fix FreeBSD x64 segfault context Issue #1401 --- core/linux/context.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/linux/context.cpp b/core/linux/context.cpp index 74136a177..6cd6c6eb9 100644 --- a/core/linux/context.cpp +++ b/core/linux/context.cpp @@ -100,6 +100,9 @@ static void context_segfault(host_context_t* hostctx, void* segfault_ctx) #elif HOST_CPU == CPU_X64 #if defined(__FreeBSD__) || defined(__DragonFly__) bicopy(hostctx->pc, MCTX(.mc_rip)); + bicopy(hostctx->rsp, MCTX(.mc_rsp)); + bicopy(hostctx->r9, MCTX(.mc_r9)); + bicopy(hostctx->rdi, MCTX(.mc_rdi)); #elif defined(__OpenBSD__) bicopy(hostctx->pc, MCTX(->sc_rip)); bicopy(hostctx->rsp, MCTX(->sc_rsp)); From b5eecb79b8fed98e8764926f489bc8c5fe7b0924 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 26 Feb 2024 16:27:18 +0100 Subject: [PATCH 24/86] android: hid barcode scanner support --- .../src/main/jni/src/android_keyboard.h | 145 ++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/shell/android-studio/flycast/src/main/jni/src/android_keyboard.h b/shell/android-studio/flycast/src/main/jni/src/android_keyboard.h index 03c50fc52..087af3a5f 100644 --- a/shell/android-studio/flycast/src/main/jni/src/android_keyboard.h +++ b/shell/android-studio/flycast/src/main/jni/src/android_keyboard.h @@ -18,6 +18,8 @@ */ #pragma once #include "input/keyboard_device.h" +#include "hw/naomi/card_reader.h" +#include const u8 AndroidKeycodes[] { /** Unknown key code. */ @@ -572,9 +574,152 @@ public: void input(int androidKeycode, bool pressed) { + if (card_reader::barcodeAvailable() && handleBarcodeScanner(androidKeycode, pressed)) + return; u8 keycode = 0; if (androidKeycode >= 0 && androidKeycode < std::size(AndroidKeycodes)) keycode = AndroidKeycodes[androidKeycode]; KeyboardDevice::input(keycode, pressed, 0); } + +private: + // All known card games use simple Code 39 barcodes. + // The barcode scanner should be configured to use HID-USB (act like a keyboard) + // and use '*' as preamble and terminator, which are the Code 39 start and stop characters. + // So disable the default terminator ('\n') and enable sending the Code 39 start and stop characters. + // Only US QWERTY layout is supported. + bool handleBarcodeScanner(int keycode, bool pressed) + { + static const std::unordered_map keymap { + { 0x3e, ' ' }, + { 0x10f, '*' }, // Shift-8 + { 0x45, '-' }, + { 0x38, '.' }, + { 0x10b, '$' }, // Shift-4 + { 0x4c, '/' }, + { 0x146, '+' }, // Shift-= + { 0x10c, '%' }, // Shift-5 + { 0x11d, 'A' }, + { 0x11e, 'B' }, + { 0x11f, 'C' }, + { 0x120, 'D' }, + { 0x121, 'E' }, + { 0x122, 'F' }, + { 0x123, 'G' }, + { 0x124, 'H' }, + { 0x125, 'I' }, + { 0x126, 'J' }, + { 0x127, 'K' }, + { 0x128, 'L' }, + { 0x129, 'M' }, + { 0x12a, 'N' }, + { 0x12b, 'O' }, + { 0x12c, 'P' }, + { 0x12d, 'Q' }, + { 0x12e, 'R' }, + { 0x12f, 'S' }, + { 0x130, 'T' }, + { 0x131, 'U' }, + { 0x132, 'V' }, + { 0x133, 'W' }, + { 0x134, 'X' }, + { 0x135, 'Y' }, + { 0x136, 'Z' }, + { 0x07, '0' }, + { 0x08, '1' }, + { 0x09, '2' }, + { 0x0a, '3' }, + { 0x0b, '4' }, + { 0x0c, '5' }, + { 0x0d, '6' }, + { 0x0e, '7' }, + { 0x0f, '8' }, + { 0x10, '9' }, + }; + + switch (keycode) + { + case 0x39: // alt left + if (pressed) + modifiers |= 4; + else + modifiers &= ~4; + return false; + case 0x3a: // alt right + if (pressed) + modifiers |= 8; + else + modifiers &= ~8; + return false; + case 0x3b: // shift left + case 0x3c: // shift right + if (pressed) + modifiers |= 1; + else + modifiers &= ~1; + return false; + case 0x71: // ctrl left + case 0x72: // ctrl right + if (pressed) + modifiers |= 2; + else + modifiers &= ~2; + return false; + + default: + break; + } + if (!pressed || (modifiers & ~1) != 0) + // Ignore key releases and unused modifiers + return false; + u16 k = keycode & 0xff; + if (modifiers & 1) + // shift + k |= 0x100; + + auto it = keymap.find(k); + if (it == keymap.end()) + { + if (!barcode.empty()) + { + INFO_LOG(INPUT, "Unrecognized barcode scancode %d mod 0x%x", keycode, modifiers); + barcode.clear(); + } + return false; + } + + double now = os_GetSeconds(); + if (!barcode.empty() && now - lastBarcodeTime >= 0.5) + { + INFO_LOG(INPUT, "Barcode timeout"); + barcode.clear(); + } + char c = it->second; + if (c == '*') + { + if (barcode.empty()) + { + DEBUG_LOG(INPUT, "Barcode start"); + barcode += '*'; + lastBarcodeTime = now; + } + else + { + card_reader::barcodeSetCard(barcode); + barcode.clear(); + card_reader::insertCard(0); + } + return true; + } + if (barcode.empty()) + return false; + + barcode += c; + lastBarcodeTime = now; + return true; + } + + int modifiers = 0; + std::string barcode; + double lastBarcodeTime = 0.0; }; From 588035d9ebb7ed161a38d9fc548e2762a15dd8ce Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Fri, 1 Mar 2024 12:00:24 +0100 Subject: [PATCH 25/86] set currently played game name in the window title Issue #28 --- core/emulator.cpp | 5 +++-- core/emulator.h | 11 +++++++---- core/hw/naomi/naomi_cart.cpp | 6 ++++-- core/sdl/sdl.cpp | 21 +++++++++++++++++++-- core/types.h | 1 + 5 files changed, 34 insertions(+), 10 deletions(-) diff --git a/core/emulator.cpp b/core/emulator.cpp index 5d7fadb30..3872bd47c 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -447,6 +447,7 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) { hostfs::FileInfo info = hostfs::storage().getFileInfo(settings.content.path); settings.content.fileName = info.name; + settings.content.title = get_file_basename(info.name); } } else @@ -504,6 +505,8 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) InitDrive(""); } } + if (settings.content.path.empty()) + settings.content.title = "Dreamcast BIOS"; if (progress) progress->progress = 1.0f; @@ -761,8 +764,6 @@ void Emulator::setNetworkState(bool online) settings.input.fastForwardMode &= !online; } -EventManager EventManager::Instance; - void EventManager::registerEvent(Event event, Callback callback, void *param) { unregisterEvent(event, callback, param); diff --git a/core/emulator.h b/core/emulator.h index 09800e2a0..e72737c63 100644 --- a/core/emulator.h +++ b/core/emulator.h @@ -54,25 +54,28 @@ public: using Callback = void (*)(Event, void *); static void listen(Event event, Callback callback, void *param = nullptr) { - Instance.registerEvent(event, callback, param); + instance().registerEvent(event, callback, param); } static void unlisten(Event event, Callback callback, void *param = nullptr) { - Instance.unregisterEvent(event, callback, param); + instance().unregisterEvent(event, callback, param); } static void event(Event event) { - Instance.broadcastEvent(event); + instance().broadcastEvent(event); } private: EventManager() = default; + static EventManager& instance() { + static EventManager _instance; + return _instance; + } void registerEvent(Event event, Callback callback, void *param); void unregisterEvent(Event event, Callback callback, void *param); void broadcastEvent(Event event); - static EventManager Instance; std::map>> callbacks; }; diff --git a/core/hw/naomi/naomi_cart.cpp b/core/hw/naomi/naomi_cart.cpp index b10bf3e75..a2978a9ea 100644 --- a/core/hw/naomi/naomi_cart.cpp +++ b/core/hw/naomi/naomi_cart.cpp @@ -627,8 +627,10 @@ void naomi_cart_LoadRom(const std::string& path, const std::string& fileName, Lo bool systemSP = memcmp(bootId.boardName, "SystemSP", 8) == 0; std::string gameId = trim_trailing_ws(std::string(bootId.gameTitle[systemSP ? 1 : 0], &bootId.gameTitle[systemSP ? 1 : 0][32])); std::string romName; - if (CurrentCartridge->game != nullptr) + if (CurrentCartridge->game != nullptr) { romName = CurrentCartridge->game->name; + settings.content.title = CurrentCartridge->game->description; + } if (gameId == "SAMPLE GAME MAX LONG NAME-") { // Use better game names @@ -885,7 +887,7 @@ void* NaomiCartridge::GetDmaPtr(u32& size) { if ((DmaOffset & 0x1fffffff) >= RomSize) { - INFO_LOG(NAOMI, "Error: DmaOffset >= RomSize"); + INFO_LOG(NAOMI, "Error: DmaOffset (%x) >= RomSize (%x)", DmaOffset, RomSize); size = 0; return nullptr; } diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index 85cf106a2..6cb4c8dc6 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -88,6 +88,14 @@ static void sdl_close_joystick(SDL_JoystickID instance) gamepad->close(); } +static void setWindowTitleGame() +{ + if (settings.naomi.slave) + SDL_SetWindowTitle(window, ("Flycast - Multiboard Slave " + cfgLoadStr("naomi", "BoardId", "")).c_str()); + else + SDL_SetWindowTitle(window, ("Flycast - " + settings.content.title).c_str()); +} + static void captureMouse(bool capture) { if (window == nullptr || !gameRunning) @@ -98,7 +106,7 @@ static void captureMouse(bool capture) SDL_SetRelativeMouseMode(SDL_FALSE); else SDL_ShowCursor(SDL_ENABLE); - SDL_SetWindowTitle(window, "Flycast"); + setWindowTitleGame(); mouseCaptured = false; } else @@ -118,12 +126,15 @@ static void emuEventCallback(Event event, void *) { switch (event) { + case Event::Terminate: + SDL_SetWindowTitle(window, "Flycast"); + break; case Event::Pause: gameRunning = false; if (!config::UseRawInput) SDL_SetRelativeMouseMode(SDL_FALSE); SDL_ShowCursor(SDL_ENABLE); - SDL_SetWindowTitle(window, "Flycast"); + setWindowTitleGame(); break; case Event::Resume: gameRunning = true; @@ -199,6 +210,9 @@ void input_sdl_init() SDL_SetRelativeMouseMode(SDL_FALSE); + // Event::Start is called on a background thread, so we can't use it to change the window title (macOS) + // However it's followed by Event::Resume which is fine. + EventManager::listen(Event::Terminate, emuEventCallback); EventManager::listen(Event::Pause, emuEventCallback); EventManager::listen(Event::Resume, emuEventCallback); @@ -234,6 +248,9 @@ void input_sdl_init() void input_sdl_quit() { + EventManager::unlisten(Event::Terminate, emuEventCallback); + EventManager::unlisten(Event::Pause, emuEventCallback); + EventManager::unlisten(Event::Resume, emuEventCallback); SDLGamepad::closeAllGamepads(); SDL_QuitSubSystem(SDL_INIT_JOYSTICK); } diff --git a/core/types.h b/core/types.h index b0cc7b388..21b9ab3d5 100644 --- a/core/types.h +++ b/core/types.h @@ -178,6 +178,7 @@ struct settings_t std::string path; std::string gameId; std::string fileName; + std::string title; } content; struct { From dac5c008ab6c7ab12da541508d628b3c4f964a37 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Fri, 1 Mar 2024 12:34:49 +0100 Subject: [PATCH 26/86] Discord Presence support Issue #1289 --- .github/workflows/c-cpp.yml | 10 ++-- .gitmodules | 3 + CMakeLists.txt | 10 ++++ core/cfg/option.cpp | 1 + core/cfg/option.h | 1 + core/deps/discord-rpc | 1 + core/discord.cpp | 107 ++++++++++++++++++++++++++++++++++++ core/rend/gui.cpp | 3 + 8 files changed, 131 insertions(+), 5 deletions(-) create mode 160000 core/deps/discord-rpc create mode 100644 core/discord.cpp diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index aaa4b214e..3350ce706 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -18,11 +18,11 @@ jobs: matrix: config: - {name: i686-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x86, cmakeArgs: -G Ninja, buildType: Release} - - {name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DAPPLE_BREAKPAD=ON, destDir: osx, buildType: RelWithDebInfo} + - {name: apple-darwin, os: macos-latest, shell: sh, cmakeArgs: -G Xcode -DAPPLE_BREAKPAD=ON -DUSE_DISCORD=ON, destDir: osx, buildType: RelWithDebInfo} - {name: apple-ios, os: macos-latest, shell: sh, cmakeArgs: -DCMAKE_SYSTEM_NAME=iOS -G Xcode, destDir: ios, buildType: Release} - - {name: x86_64-pc-linux-gnu, os: ubuntu-20.04, shell: sh, cmakeArgs: -G Ninja, destDir: linux, buildType: RelWithDebInfo} - - {name: x86_64-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x64, cmakeArgs: -G Ninja, buildType: Release} - - {name: x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -G Ninja, destDir: win, buildType: RelWithDebInfo} + - {name: x86_64-pc-linux-gnu, os: ubuntu-20.04, shell: sh, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, destDir: linux, buildType: RelWithDebInfo} + - {name: x86_64-pc-windows-msvc, os: windows-latest, shell: cmd, arch: x64, cmakeArgs: -G Ninja -DUSE_DISCORD=ON, buildType: Release} + - {name: x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -G Ninja -DUSE_DISCORD=ON, destDir: win, buildType: RelWithDebInfo} - {name: libretro-x86_64-pc-linux-gnu, os: ubuntu-latest, shell: sh, cmakeArgs: -DLIBRETRO=ON -DCMAKE_POSITION_INDEPENDENT_CODE=TRUE -G Ninja, buildType: Release} - {name: libretro-x86_64-w64-mingw32, os: windows-latest, shell: 'msys2 {0}', cmakeArgs: -DLIBRETRO=ON -G Ninja, buildType: Release} @@ -69,7 +69,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - submodules: true + submodules: recursive - name: Compile a universal OpenMP (macOS) run: HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew reinstall --build-from-source --formula ./shell/apple/libomp.rb diff --git a/.gitmodules b/.gitmodules index c4747d15b..e8637dee5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -29,3 +29,6 @@ [submodule "core/deps/Spout"] path = core/deps/Spout url = https://github.com/vkedwardli/Spout2.git +[submodule "core/deps/discord-rpc"] + path = core/deps/discord-rpc + url = https://github.com/flyinghead/discord-rpc diff --git a/CMakeLists.txt b/CMakeLists.txt index 49f312f2e..cf4a6de42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,6 +68,7 @@ option(APPLE_BREAKPAD "macOS: Build breakpad client and dump symbols" OFF) option(ENABLE_GDB_SERVER "Build with GDB debugging support" OFF) option(ENABLE_DC_PROFILER "Build with support for target machine (SH4) profiler" OFF) option(ENABLE_FC_PROFILER "Build with support for host app (Flycast) profiler" OFF) +option(USE_DISCORD "Use Discord Presence API" OFF) if(IOS AND NOT LIBRETRO) set(USE_VULKAN OFF CACHE BOOL "Force vulkan off" FORCE) @@ -716,6 +717,15 @@ endif() target_sources(${PROJECT_NAME} PRIVATE core/deps/xbrz/xbrz.cpp) target_sources(${PROJECT_NAME} PRIVATE core/deps/md5/md5.cpp) +if(USE_DISCORD) + option(BUILD_EXAMPLES "Build example apps" OFF) + add_subdirectory(core/deps/discord-rpc) + target_include_directories(${PROJECT_NAME} PRIVATE core/deps/discord-rpc/include) + target_link_libraries(${PROJECT_NAME} PRIVATE discord-rpc) + target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DISCORD) + target_sources(${PROJECT_NAME} PRIVATE core/discord.cpp) +endif() + cmrc_add_resource_library(flycast-resources ALIAS flycast::res NAMESPACE flycast) target_link_libraries(${PROJECT_NAME} PRIVATE flycast::res) diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index 8ef86252a..8c4351395 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -129,6 +129,7 @@ Option OpenGlChecks("OpenGlChecks", false, "validate"); Option, false> ContentPath("Dreamcast.ContentPath"); Option HideLegacyNaomiRoms("Dreamcast.HideLegacyNaomiRoms", true); Option UploadCrashLogs("UploadCrashLogs", true); +Option DiscordPresence("DiscordPresence", true); // Profiler Option ProfilerEnabled("Profiler.Enabled"); diff --git a/core/cfg/option.h b/core/cfg/option.h index 78a2ff677..5c3155946 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -495,6 +495,7 @@ extern Option OpenGlChecks; extern Option, false> ContentPath; extern Option HideLegacyNaomiRoms; extern Option UploadCrashLogs; +extern Option DiscordPresence; // Profiling extern Option ProfilerEnabled; diff --git a/core/deps/discord-rpc b/core/deps/discord-rpc new file mode 160000 index 000000000..c1197e1a1 --- /dev/null +++ b/core/deps/discord-rpc @@ -0,0 +1 @@ +Subproject commit c1197e1a1e2ff09c077e84541bd88cf90581648c diff --git a/core/discord.cpp b/core/discord.cpp new file mode 100644 index 000000000..66a2d55f8 --- /dev/null +++ b/core/discord.cpp @@ -0,0 +1,107 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#include "types.h" +#include "emulator.h" +#include "stdclass.h" +#include "cfg/option.h" +#include "discord_rpc.h" +#include +#include + +#define FLYCAST_APPID "1212789289851559946" + +class DiscordPresence +{ +public: + DiscordPresence() + { + EventManager::listen(Event::Start, handleEmuEvent, this); + EventManager::listen(Event::Terminate, handleEmuEvent, this); + EventManager::listen(Event::Resume, handleEmuEvent, this); + } + + ~DiscordPresence() + { + shutdown(); + EventManager::unlisten(Event::Start, handleEmuEvent, this); + EventManager::unlisten(Event::Terminate, handleEmuEvent, this); + EventManager::unlisten(Event::Resume, handleEmuEvent, this); + } + +private: + void initialize() + { + if (!initialized) + Discord_Initialize(FLYCAST_APPID, nullptr, 0, nullptr); + initialized = true; + } + + void shutdown() + { + if (initialized) + Discord_Shutdown(); + initialized = false; + } + + void sendPresence() + { + initialize(); + DiscordRichPresence discordPresence{}; + std::string state = settings.content.title.substr(0, 128); + discordPresence.state = state.c_str(); + discordPresence.startTimestamp = time(0); + discordPresence.largeImageKey = "icon-512"; + Discord_UpdatePresence(&discordPresence); + } + + static void handleEmuEvent(Event event, void *p) + { + if (settings.naomi.slave || settings.naomi.drivingSimSlave != 0) + return; + DiscordPresence *inst = (DiscordPresence *)p; + switch (event) + { + case Event::Start: + if (config::DiscordPresence) + inst->sendPresence(); + break; + case Event::Resume: + if (config::DiscordPresence && !inst->initialized) + // Discord presence enabled + inst->sendPresence(); + else if (!config::DiscordPresence && inst->initialized) + { + // Discord presence disabled + Discord_ClearPresence(); + inst->shutdown(); + } + break; + case Event::Terminate: + if (inst->initialized) + Discord_ClearPresence(); + break; + default: + break; + } + } + + bool initialized = false; +}; + +static DiscordPresence discordPresence; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 3e559ee63..ac16939ac 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -1694,6 +1694,9 @@ static void gui_display_settings() OptionCheckbox("Save", config::AutoSaveState, "Save the state of the game when stopping"); OptionCheckbox("Naomi Free Play", config::ForceFreePlay, "Configure Naomi games in Free Play mode."); +#if USE_DISCORD + OptionCheckbox("Discord Presence", config::DiscordPresence, "Show which game you are playing on Discord"); +#endif ImGui::PopStyleVar(); ImGui::EndTabItem(); From 62d00578af7e86e9544b75fa4b2132bf472e5595 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Fri, 1 Mar 2024 12:58:53 +0100 Subject: [PATCH 27/86] fix compile warnings --- core/hw/naomi/systemsp.cpp | 2 +- core/reios/reios_elf.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/hw/naomi/systemsp.cpp b/core/hw/naomi/systemsp.cpp index f356a6f09..9cc51b366 100644 --- a/core/hw/naomi/systemsp.cpp +++ b/core/hw/naomi/systemsp.cpp @@ -1300,7 +1300,7 @@ class MedalIOManager : public DefaultIOManager } // OUT-1 (CN10 17-24) - void setCN10_17_24(u8 v) + void setCN10_17_24(u8 v) override { // 0: sw.lamp c // 1: jp solenoid diff --git a/core/reios/reios_elf.cpp b/core/reios/reios_elf.cpp index 30612b743..4f621d6fe 100644 --- a/core/reios/reios_elf.cpp +++ b/core/reios/reios_elf.cpp @@ -47,10 +47,10 @@ bool reios_loadElf(const std::string& elf) { u8* ptr = GetMemPtr(dest, len); if (ptr == NULL) { - WARN_LOG(REIOS, "Invalid load address for section %d: %08lx", i, (long)dest); + WARN_LOG(REIOS, "Invalid load address for section %d: %08lx", (int)i, (long)dest); continue; } - DEBUG_LOG(REIOS, "Loading section %d to %08lx - %08lx", i, (long)dest, (long)(dest + len - 1)); + DEBUG_LOG(REIOS, "Loading section %d to %08lx - %08lx", (int)i, (long)dest, (long)(dest + len - 1)); memcpy(ptr, src, len); ptr += len; memset(ptr, 0, elf_getProgramHeaderMemorySize(&elfFile, i) - len); From b0a268b89ee730fbe18031779ed3a51e5597c67c Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 3 Mar 2024 16:03:22 +0100 Subject: [PATCH 28/86] richer discord presence using boxart from thegamesdb --- CMakeLists.txt | 4 ++-- core/emulator.cpp | 5 ++++- core/emulator.h | 1 + core/rend/boxart/boxart.cpp | 13 +++++++++++++ core/rend/boxart/boxart.h | 1 + core/rend/boxart/gamesdb.cpp | 7 ++++++- core/rend/boxart/scraper.h | 3 +++ core/{ => rend}/discord.cpp | 29 +++++++++++++++++++++++++---- core/rend/gui.cpp | 18 ++++++++++++++++-- core/rend/gui.h | 1 + 10 files changed, 72 insertions(+), 10 deletions(-) rename core/{ => rend}/discord.cpp (70%) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf4a6de42..a5b23b8a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -717,13 +717,13 @@ endif() target_sources(${PROJECT_NAME} PRIVATE core/deps/xbrz/xbrz.cpp) target_sources(${PROJECT_NAME} PRIVATE core/deps/md5/md5.cpp) -if(USE_DISCORD) +if(USE_DISCORD AND NOT LIBRETRO) option(BUILD_EXAMPLES "Build example apps" OFF) add_subdirectory(core/deps/discord-rpc) target_include_directories(${PROJECT_NAME} PRIVATE core/deps/discord-rpc/include) target_link_libraries(${PROJECT_NAME} PRIVATE discord-rpc) target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DISCORD) - target_sources(${PROJECT_NAME} PRIVATE core/discord.cpp) + target_sources(${PROJECT_NAME} PRIVATE core/rend/discord.cpp) endif() cmrc_add_resource_library(flycast-resources ALIAS flycast::res NAMESPACE flycast) diff --git a/core/emulator.cpp b/core/emulator.cpp index 3872bd47c..5fa94b492 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -447,7 +447,8 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) { hostfs::FileInfo info = hostfs::storage().getFileInfo(settings.content.path); settings.content.fileName = info.name; - settings.content.title = get_file_basename(info.name); + if (settings.content.title.empty()) + settings.content.title = get_file_basename(info.name); } } else @@ -615,6 +616,7 @@ void Emulator::unloadGame() settings.content.path.clear(); settings.content.gameId.clear(); settings.content.fileName.clear(); + settings.content.title.clear(); settings.platform.system = DC_PLATFORM_DREAMCAST; state = Init; EventManager::event(Event::Terminate); @@ -760,6 +762,7 @@ void Emulator::setNetworkState(bool online) config::Sh4Clock.override(200); sh4_cpu.ResetCache(); } + EventManager::event(Event::Network); } settings.input.fastForwardMode &= !online; } diff --git a/core/emulator.h b/core/emulator.h index e72737c63..96c7d36c8 100644 --- a/core/emulator.h +++ b/core/emulator.h @@ -46,6 +46,7 @@ enum class Event { Terminate, LoadState, VBlank, + Network, }; class EventManager diff --git a/core/rend/boxart/boxart.cpp b/core/rend/boxart/boxart.cpp index 7ec447d7d..973cb9d5b 100644 --- a/core/rend/boxart/boxart.cpp +++ b/core/rend/boxart/boxart.cpp @@ -22,6 +22,19 @@ #include GameBoxart Boxart::getBoxart(const GameMedia& media) +{ + loadDatabase(); + GameBoxart boxart; + { + std::lock_guard guard(mutex); + auto it = games.find(media.fileName); + if (it != games.end()) + boxart = it->second; + } + return boxart; +} + +GameBoxart Boxart::getBoxartAndLoad(const GameMedia& media) { loadDatabase(); GameBoxart boxart; diff --git a/core/rend/boxart/boxart.h b/core/rend/boxart/boxart.h index a19680b23..2f91dc424 100644 --- a/core/rend/boxart/boxart.h +++ b/core/rend/boxart/boxart.h @@ -32,6 +32,7 @@ struct GameMedia; class Boxart { public: + GameBoxart getBoxartAndLoad(const GameMedia& media); GameBoxart getBoxart(const GameMedia& media); void saveDatabase(bool internal = false); diff --git a/core/rend/boxart/gamesdb.cpp b/core/rend/boxart/gamesdb.cpp index b15d40692..6fd20a83d 100644 --- a/core/rend/boxart/gamesdb.cpp +++ b/core/rend/boxart/gamesdb.cpp @@ -226,12 +226,14 @@ void TheGamesDb::parseBoxart(GameBoxart& item, const json& j, int gameId) { copyFile(cached->second, filename); item.setBoxartPath(filename); + item.boxartUrl = url; } else { if (downloadImage(url, filename)) { item.setBoxartPath(filename); + item.boxartUrl = url; boxartCache[url] = filename; } } @@ -394,8 +396,11 @@ void TheGamesDb::scrape(std::vector& items) else if (item.gamePath.empty()) { std::string localPath = makeUniqueFilename("dreamcast_logo_grey.png"); - if (downloadImage("https://flyinghead.github.io/flycast-builds/dreamcast_logo_grey.png", localPath)) + std::string biosArtUrl{ "https://flyinghead.github.io/flycast-builds/dreamcast_logo_grey.png" }; + if (downloadImage(biosArtUrl, localPath)) { item.setBoxartPath(localPath); + item.boxartUrl = biosArtUrl; + } } item.scraped = true; } diff --git a/core/rend/boxart/scraper.h b/core/rend/boxart/scraper.h index d6a8f81ce..321095500 100644 --- a/core/rend/boxart/scraper.h +++ b/core/rend/boxart/scraper.h @@ -38,6 +38,7 @@ struct GameBoxart std::string gamePath; std::string boxartPath; + std::string boxartUrl; bool parsed = false; bool scraped = false; @@ -56,6 +57,7 @@ struct GameBoxart { "release_date", releaseDate }, { "overview", overview }, { "boxart_path", boxartPath }, + { "boxart_url", boxartUrl }, { "parsed", parsed }, { "scraped", scraped }, }; @@ -84,6 +86,7 @@ struct GameBoxart loadProperty(releaseDate, j, "release_date"); loadProperty(overview, j, "overview"); loadProperty(boxartPath, j, "boxart_path"); + loadProperty(boxartUrl, j, "boxart_url"); loadProperty(parsed, j, "parsed"); loadProperty(scraped, j, "scraped"); } diff --git a/core/discord.cpp b/core/rend/discord.cpp similarity index 70% rename from core/discord.cpp rename to core/rend/discord.cpp index 66a2d55f8..a447e311d 100644 --- a/core/discord.cpp +++ b/core/rend/discord.cpp @@ -20,6 +20,7 @@ #include "emulator.h" #include "stdclass.h" #include "cfg/option.h" +#include "gui.h" #include "discord_rpc.h" #include #include @@ -34,6 +35,7 @@ public: EventManager::listen(Event::Start, handleEmuEvent, this); EventManager::listen(Event::Terminate, handleEmuEvent, this); EventManager::listen(Event::Resume, handleEmuEvent, this); + EventManager::listen(Event::Network, handleEmuEvent, this); } ~DiscordPresence() @@ -42,6 +44,7 @@ public: EventManager::unlisten(Event::Start, handleEmuEvent, this); EventManager::unlisten(Event::Terminate, handleEmuEvent, this); EventManager::unlisten(Event::Resume, handleEmuEvent, this); + EventManager::unlisten(Event::Network, handleEmuEvent, this); } private: @@ -63,10 +66,23 @@ private: { initialize(); DiscordRichPresence discordPresence{}; - std::string state = settings.content.title.substr(0, 128); - discordPresence.state = state.c_str(); - discordPresence.startTimestamp = time(0); - discordPresence.largeImageKey = "icon-512"; + discordPresence.state = settings.content.title.c_str(); + discordPresence.startTimestamp = startTimestamp; + std::string imageUrl = gui_getCurGameBoxartUrl(); + if (!imageUrl.empty()) + { + discordPresence.largeImageKey = imageUrl.c_str(); + discordPresence.largeImageText = settings.content.title.c_str(); + discordPresence.smallImageKey = "icon-512"; + discordPresence.smallImageText = "Flycast is a Dreamcast, Naomi and Atomiswave emulator"; + } + else + { + discordPresence.largeImageKey = "icon-512"; + discordPresence.largeImageText = "Flycast is a Dreamcast, Naomi and Atomiswave emulator"; + } + if (settings.network.online) + discordPresence.details = "Online"; Discord_UpdatePresence(&discordPresence); } @@ -78,6 +94,9 @@ private: switch (event) { case Event::Start: + inst->startTimestamp = time(nullptr); + [[fallthrough]]; + case Event::Network: if (config::DiscordPresence) inst->sendPresence(); break; @@ -95,6 +114,7 @@ private: case Event::Terminate: if (inst->initialized) Discord_ClearPresence(); + inst->startTimestamp = 0; break; default: break; @@ -102,6 +122,7 @@ private: } bool initialized = false; + int64_t startTimestamp = 0; }; static DiscordPresence discordPresence; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index ac16939ac..ae919b865 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -2852,7 +2852,7 @@ static void gui_display_content() { ImTextureID textureId{}; GameMedia game; - GameBoxart art = boxart.getBoxart(game); + GameBoxart art = boxart.getBoxartAndLoad(game); if (getGameImage(art, textureId, loadedImages < 10)) loadedImages++; if (textureId != ImTextureID()) @@ -2885,7 +2885,7 @@ static void gui_display_content() GameBoxart art; if (config::BoxartDisplayMode) { - art = boxart.getBoxart(game); + art = boxart.getBoxartAndLoad(game); gameName = art.name; } if (filter.PassFilter(gameName.c_str())) @@ -2927,6 +2927,11 @@ static void gui_display_content() } else { + if (!config::BoxartDisplayMode) + art = boxart.getBoxart(game); + settings.content.title = art.name; + if (settings.content.title.empty() || settings.content.title == game.fileName) + settings.content.title = get_file_basename(game.fileName); std::string gamePath(game.path); scanner.get_mutex().unlock(); gui_start_game(gamePath); @@ -3399,6 +3404,15 @@ void gui_setState(GuiState newState) } } +std::string gui_getCurGameBoxartUrl() +{ + GameMedia game; + game.fileName = settings.content.fileName; + game.path = settings.content.path; + GameBoxart art = boxart.getBoxart(game); + return art.boxartUrl; +} + #ifdef TARGET_UWP // Ugly but a good workaround for MS stupidity // UWP doesn't allow the UI thread to wait on a thread/task. When an std::future is ready, it is possible diff --git a/core/rend/gui.h b/core/rend/gui.h index cfd03a0f4..99ffc9b88 100644 --- a/core/rend/gui.h +++ b/core/rend/gui.h @@ -50,6 +50,7 @@ void gui_setOnScreenKeyboardCallback(void (*callback)(bool show)); void gui_save(); void gui_loadState(); void gui_saveState(); +std::string gui_getCurGameBoxartUrl(); enum class GuiState { Closed, From 8252a28e48933ce1beae0bb6e32e4be3ad1d3760 Mon Sep 17 00:00:00 2001 From: Bobby Smith <33353403+bslenul@users.noreply.github.com> Date: Sat, 16 Mar 2024 23:48:26 +0100 Subject: [PATCH 29/86] [Libretro] Check for per-pixel compatibility and hide the option if not supported --- core/rend/vulkan/vk_context_lr.cpp | 4 +-- core/rend/vulkan/vk_context_lr.h | 2 ++ shell/libretro/libretro.cpp | 47 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/core/rend/vulkan/vk_context_lr.cpp b/core/rend/vulkan/vk_context_lr.cpp index 3a293151a..9051d1891 100644 --- a/core/rend/vulkan/vk_context_lr.cpp +++ b/core/rend/vulkan/vk_context_lr.cpp @@ -126,7 +126,7 @@ bool VkCreateDevice(retro_vulkan_context* context, VkInstance instance, VkPhysic vk::PhysicalDeviceFeatures supportedFeatures; physicalDevice.getFeatures(&supportedFeatures); - bool fragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics; + VulkanContext::Instance()->fragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics; VulkanContext::Instance()->samplerAnisotropy = supportedFeatures.samplerAnisotropy; // Enable VK_KHR_dedicated_allocation if available @@ -157,7 +157,7 @@ bool VkCreateDevice(retro_vulkan_context* context, VkInstance instance, VkPhysic vk::DeviceQueueCreateInfo(vk::DeviceQueueCreateFlags(), context->presentation_queue_family_index, 1, &queuePriority), }; vk::PhysicalDeviceFeatures features(*required_features); - if (fragmentStoresAndAtomics) + if (VulkanContext::Instance()->fragmentStoresAndAtomics) features.fragmentStoresAndAtomics = true; if (VulkanContext::Instance()->samplerAnisotropy) features.samplerAnisotropy = true; diff --git a/core/rend/vulkan/vk_context_lr.h b/core/rend/vulkan/vk_context_lr.h index 8fa3f9de9..ddfeec52f 100644 --- a/core/rend/vulkan/vk_context_lr.h +++ b/core/rend/vulkan/vk_context_lr.h @@ -89,6 +89,7 @@ public: static VulkanContext *Instance() { return contextInstance; } bool SupportsSamplerAnisotropy() const { return samplerAnisotropy; } bool SupportsDedicatedAllocation() const { return dedicatedAllocationSupported; } + bool hasPerPixel() override { return fragmentStoresAndAtomics; } const VMAllocator& GetAllocator() const { return allocator; } vk::DeviceSize GetMaxMemoryAllocationSize() const { return maxMemoryAllocationSize; } f32 GetMaxSamplerAnisotropy() const { return samplerAnisotropy ? maxSamplerAnisotropy : 1.f; } @@ -126,6 +127,7 @@ public: bool samplerAnisotropy = false; f32 maxSamplerAnisotropy = 0.f; bool dedicatedAllocationSupported = false; + bool fragmentStoresAndAtomics = false; private: u32 vendorID = 0; diff --git a/shell/libretro/libretro.cpp b/shell/libretro/libretro.cpp index aece2c6c4..d28ba5c7a 100644 --- a/shell/libretro/libretro.cpp +++ b/shell/libretro/libretro.cpp @@ -125,6 +125,9 @@ static bool platformIsDreamcast = true; static bool platformIsArcade = false; static bool threadedRenderingEnabled = true; static bool oitEnabled = false; +#if defined(HAVE_OIT) || defined(HAVE_VULKAN) || defined(HAVE_D3D11) +static bool perPixelChecked = false; +#endif static bool autoSkipFrameEnabled = false; #ifdef _OPENMP static bool textureUpscaleEnabled = false; @@ -1241,6 +1244,42 @@ void retro_reset() emu.start(); } +#if defined(HAVE_OIT) || defined(HAVE_VULKAN) || defined(HAVE_D3D11) +void check_per_pixel_opt(void) +{ + // Check if per-pixel is supported, if not we hide the option + if (!GraphicsContext::Instance()->hasPerPixel()) + { + for (unsigned i = 0; option_defs_us[i].key != NULL; i++) + { + // Looking for the alpha sorting core option... + if (!strcmp(option_defs_us[i].key, CORE_OPTION_NAME "_alpha_sorting")) + { + for (unsigned j = 0; option_defs_us[i].values[j].value != NULL; j++) + { + // ... then for the per-pixel choice... + if (!strcmp(option_defs_us[i].values[j].value, "per-pixel (accurate)")) + { + // ... null it out... + option_defs_us[i].values[j] = { NULL, NULL }; + + // ... and finally refresh core options. + bool optionCategoriesSupported = false; + libretro_set_core_options(environ_cb, &optionCategoriesSupported); + categoriesSupported |= optionCategoriesSupported; + + break; + } + } + break; + } + } + NOTICE_LOG(RENDERER, "Current renderer does not support 'Per-Pixel' Alpha Sorting."); + } + perPixelChecked = true; +} +#endif + #if defined(HAVE_OPENGL) || defined(HAVE_OPENGLES) static void context_reset() { @@ -1251,6 +1290,10 @@ static void context_reset() rend_term_renderer(); theGLContext.init(); rend_init_renderer(); +#ifdef HAVE_OIT + if (!perPixelChecked) + check_per_pixel_opt(); +#endif } static void context_destroy() @@ -1812,6 +1855,8 @@ static void retro_vk_context_reset() theVulkanContext.init((retro_hw_render_interface_vulkan *)vulkan); rend_term_renderer(); rend_init_renderer(); + if (!perPixelChecked) + check_per_pixel_opt(); } static void retro_vk_context_destroy() @@ -1946,6 +1991,8 @@ static void dx11_context_reset() else if (config::RendererType != RenderType::DirectX11_OIT) config::RendererType = RenderType::DirectX11; rend_init_renderer(); + if (!perPixelChecked) + check_per_pixel_opt(); } static void dx11_context_destroy() From 41d418ed253b41dfe6b2ccfd4a4dab8074fd6cb3 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 6 Apr 2024 17:21:27 +0200 Subject: [PATCH 30/86] bump libchdr to match master --- core/deps/libchdr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/deps/libchdr b/core/deps/libchdr index 2a1119c68..7239eab39 160000 --- a/core/deps/libchdr +++ b/core/deps/libchdr @@ -1 +1 @@ -Subproject commit 2a1119c686eb07033d02f8c6d12406f8fd373775 +Subproject commit 7239eab39c961a27959cfb58c8877cb2ab1fbf2d From 4f87a3556e19215475f82ef951c4cedd77a8087c Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 6 Apr 2024 17:43:19 +0200 Subject: [PATCH 31/86] Fix vmu beep frequency --- core/hw/aica/sgc_if.cpp | 100 +++++++++++++++++++---------------- core/serialize.h | 3 +- tests/src/serialize_test.cpp | 2 +- 3 files changed, 57 insertions(+), 48 deletions(-) diff --git a/core/hw/aica/sgc_if.cpp b/core/hw/aica/sgc_if.cpp index 013cebea4..505fcce48 100755 --- a/core/hw/aica/sgc_if.cpp +++ b/core/hw/aica/sgc_if.cpp @@ -149,80 +149,88 @@ class VmuBeep public: void init() { - beepOn = 0; - beepPeriod = 0; - beepCounter = 0; - beepValue = 0; + active = false; + att = 256; + waveIdx.full = 0; + waveStep = 0; } - void update(int on, int period) + void update(int period, int on) { - if (on == 0 || period == 0 || on < period) - { - beepOn = 0; - beepPeriod = 0; + if (on == 0 || period == 0 || on >= period) { + active = false; } else { - // The maple doc may be wrong on this. It looks like the raw values of T1LR and T1LC are set. - // So the period is (256 - T1LR) / (32768 / 6) - // and the duty cycle is (256 - T1LC) / (32768 / 6) - beepOn = (256 - on) * 8; - beepPeriod = (256 - period) * 8; - beepCounter = 0; + // on (duty cycle) is ignored + active = true; + // 6 MHz clock + int freq = 6000000 / 6 / period; + waveStep = freq * 1024 / 2698; } } SampleType getSample() { - constexpr int Slope = 500; - if (beepPeriod == 0) - { - if (beepValue > 0) - beepValue = std::max(0, beepValue - Slope); - else if (beepValue < 0) - beepValue = std::min(0, beepValue + Slope); - } - else - { - if (beepCounter <= beepOn) - beepValue = std::min(16383, beepValue + Slope); - else - beepValue = std::max(-16384, beepValue - Slope); - beepCounter = (beepCounter + 1) % beepPeriod; - } + if (!active && att >= 256) + return 0; - return beepValue; + waveIdx.full += waveStep; + waveIdx.ip %= std::size(wave); + int nextIdx = (waveIdx.ip + 1) % std::size(wave); + SampleType s = (FPMul(wave[waveIdx.ip], 1024 - waveIdx.fp, 10) + FPMul(wave[nextIdx], (int)waveIdx.fp, 10)) * 2; + + s = FPMul(s, tl_lut[att], 15); + if (active) + att = std::max(att - 2, 0); + else + att = std::min(att + 2, 256); + return s; } void serialize(Serializer& ser) { - ser << beepOn; - ser << beepPeriod; - ser << beepCounter; + ser << active; + ser << att; + ser << waveIdx; + ser << waveStep; } void deserialize(Deserializer& deser) { - if (deser.version() >= Deserializer::V22) + if (deser.version() < Deserializer::V49) { - deser >> beepOn; - deser >> beepPeriod; - deser >> beepCounter; + if (deser.version() >= Deserializer::V22) + { + deser.skip(); // beepOn + deser.skip(); // beepPeriod + deser.skip(); // beepCounter + } + init(); } else { - beepOn = 0; - beepPeriod = 0; - beepCounter = 0; + deser >> active; + deser >> att; + deser >> waveIdx; + deser >> waveStep; } } private: - int beepOn = 0; - int beepPeriod = 0; - int beepCounter = 0; - SampleType beepValue = 0; + bool active = false; + int att = 256; + fp_22_10 waveIdx {}; + int waveStep = 0; + + // 2698 Hz + static constexpr SampleType wave[] = { 503, -3519, + -7540, -8214, -8209, -8214, -8209, -5199, -1172, 2843, + 6873, 8207, 8214, 8209, 8212, 5866, 1840, -2175, + -6203, -8210, -8215, -8209, -8214, -6533, -2516, 1507, + 5526, 8212, 8210, 8212, 8210, 7206, 3187, -841, + -4856, -8215, -8209, -8214, -8208, -7882, -3850, 162, + 4187, 8213, 8208, 8213, 8209, 8211, 4525 }; }; static VmuBeep beep; diff --git a/core/serialize.h b/core/serialize.h index 5b9d38506..89ac59ae2 100644 --- a/core/serialize.h +++ b/core/serialize.h @@ -74,7 +74,8 @@ public: V46, V47, V48, - Current = V48, + V49, + Current = V49, Next = Current + 1, }; diff --git a/tests/src/serialize_test.cpp b/tests/src/serialize_test.cpp index a057e375f..1ef30fc84 100644 --- a/tests/src/serialize_test.cpp +++ b/tests/src/serialize_test.cpp @@ -32,7 +32,7 @@ TEST_F(SerializeTest, SizeTest) std::vector data(30000000); Serializer ser(data.data(), data.size()); dc_serialize(ser); - ASSERT_EQ(28191438u, ser.size()); + ASSERT_EQ(28191439u, ser.size()); } From eaa22a49d1ba662d5b81328eca170c5c0ce25064 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 6 Apr 2024 19:07:27 +0200 Subject: [PATCH 32/86] msvc build fix --- core/hw/aica/sgc_if.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/hw/aica/sgc_if.cpp b/core/hw/aica/sgc_if.cpp index 505fcce48..7b5c4755e 100755 --- a/core/hw/aica/sgc_if.cpp +++ b/core/hw/aica/sgc_if.cpp @@ -178,7 +178,7 @@ public: waveIdx.full += waveStep; waveIdx.ip %= std::size(wave); int nextIdx = (waveIdx.ip + 1) % std::size(wave); - SampleType s = (FPMul(wave[waveIdx.ip], 1024 - waveIdx.fp, 10) + FPMul(wave[nextIdx], (int)waveIdx.fp, 10)) * 2; + SampleType s = (FPMul(wave[waveIdx.ip], (int)(1024 - waveIdx.fp), 10) + FPMul(wave[nextIdx], (int)waveIdx.fp, 10)) * 2; s = FPMul(s, tl_lut[att], 15); if (active) From e8340bfa5ee94348f1bf9f65ce3e846268ebef61 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 7 Apr 2024 11:47:42 +0200 Subject: [PATCH 33/86] maple: implement AllStatusReq for controller and VMU --- core/hw/maple/maple_devs.cpp | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/core/hw/maple/maple_devs.cpp b/core/hw/maple/maple_devs.cpp index 3148db8d2..7b63d615e 100755 --- a/core/hw/maple/maple_devs.cpp +++ b/core/hw/maple/maple_devs.cpp @@ -154,7 +154,15 @@ struct maple_sega_controller: maple_base //2 (Maximum current consumption) w16(get_device_current(1)); - return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll; + if (cmd == MDC_AllStatusReq) + { + const char *extra = "Version 1.010,1998/09/28,315-6211-AB ,Analog Module : The 4th Edition.5/8 +DF"; + wptr(extra, strlen(extra)); + return MDRS_DeviceStatusAll; + } + else { + return MDRS_DeviceStatus; + } //controller condition case MDCF_GetCondition: @@ -441,7 +449,15 @@ struct maple_sega_vmu: maple_base //2 w16(0x0082); // 13 mA - return cmd == MDC_DeviceRequest ? MDRS_DeviceStatus : MDRS_DeviceStatusAll; + if (cmd == MDC_AllStatusReq) + { + const char *extra = "Version 1.005,1999/04/15,315-6208-03,SEGA Visual Memory System BIOS Produced by "; + wptr(extra, strlen(extra)); + return MDRS_DeviceStatusAll; + } + else { + return MDRS_DeviceStatus; + } //in[0] is function used //out[0] is function used From a6c4530e222c094e6e477faab06724ce4a21dff8 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 7 Apr 2024 12:27:54 +0200 Subject: [PATCH 34/86] vulkan: allow custom GPU driver loading with libadrenotools Issue #1471 --- .gitmodules | 3 + CMakeLists.txt | 9 +- core/archive/ZipArchive.cpp | 16 +- core/archive/ZipArchive.h | 16 +- core/archive/archive.h | 1 + core/cfg/option.cpp | 1 + core/cfg/option.h | 1 + core/deps/libadrenotools | 1 + core/rend/gui.cpp | 72 ++++++ core/rend/vulkan/adreno.cpp | 209 ++++++++++++++++++ core/rend/vulkan/adreno.h | 25 +++ core/rend/vulkan/vulkan_context.cpp | 13 +- shell/android-studio/flycast/build.gradle | 4 + .../com/reicast/emulator/BaseGLActivity.java | 8 + .../flycast/src/main/jni/src/Android.cpp | 16 ++ 15 files changed, 384 insertions(+), 11 deletions(-) create mode 160000 core/deps/libadrenotools create mode 100644 core/rend/vulkan/adreno.cpp create mode 100644 core/rend/vulkan/adreno.h diff --git a/.gitmodules b/.gitmodules index e8637dee5..77b577576 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ [submodule "core/deps/discord-rpc"] path = core/deps/discord-rpc url = https://github.com/flyinghead/discord-rpc +[submodule "core/deps/libadrenotools"] + path = core/deps/libadrenotools + url = https://github.com/bylaws/libadrenotools diff --git a/CMakeLists.txt b/CMakeLists.txt index 9749fbee3..1bf2241af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,9 +105,10 @@ if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") string(REPLACE "-" "." MS_VERSION ${MS_VERSION}) string(REGEX REPLACE "\.g[0-9a-f]+" "" MS_VERSION ${MS_VERSION}) string(REGEX MATCH "[0-9]+\.[0-9]+\.[0-9]+" VERSION_3PARTS ${MS_VERSION}) + string(REGEX MATCH "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" VERSION_4PARTS ${MS_VERSION}) if(VERSION_3PARTS STREQUAL "") string(APPEND MS_VERSION ".0.0") - else() + elseif(VERSION_4PARTS STREQUAL "") string(APPEND MS_VERSION ".0") endif() endif() @@ -1283,6 +1284,11 @@ if(USE_VULKAN) target_compile_options(VulkanMemoryAllocator INTERFACE $<$,$>:-Wno-nullability-completeness>) target_link_libraries(${PROJECT_NAME} PRIVATE GPUOpen::VulkanMemoryAllocator) + if(ANDROID AND NOT LIBRETRO AND "arm64" IN_LIST ARCHITECTURE) + add_subdirectory(core/deps/libadrenotools) + target_link_libraries(${PROJECT_NAME} PRIVATE adrenotools) + endif() + target_compile_definitions(${PROJECT_NAME} PRIVATE USE_VULKAN HAVE_VULKAN) target_sources(${PROJECT_NAME} PRIVATE core/rend/vulkan/oit/oit_buffer.h @@ -1295,6 +1301,7 @@ if(USE_VULKAN) core/rend/vulkan/oit/oit_renderpass.h core/rend/vulkan/oit/oit_shaders.cpp core/rend/vulkan/oit/oit_shaders.h + core/rend/vulkan/adreno.cpp core/rend/vulkan/buffer.cpp core/rend/vulkan/buffer.h core/rend/vulkan/commandpool.cpp diff --git a/core/archive/ZipArchive.cpp b/core/archive/ZipArchive.cpp index cd279191c..ef10710f2 100644 --- a/core/archive/ZipArchive.cpp +++ b/core/archive/ZipArchive.cpp @@ -47,7 +47,7 @@ ArchiveFile* ZipArchive::OpenFile(const char* name) return nullptr; zip_stat_t stat; zip_stat(zip, name, 0, &stat); - return new ZipArchiveFile(zip_file, stat.size); + return new ZipArchiveFile(zip_file, stat.size, stat.name); } static zip_file *zip_fopen_by_crc(zip_t *za, u32 crc, int flags, zip_uint64_t& index) @@ -77,7 +77,7 @@ ArchiveFile* ZipArchive::OpenFileByCrc(u32 crc) zip_stat_t stat; zip_stat_index(zip, index, 0, &stat); - return new ZipArchiveFile(zip_file, stat.size); + return new ZipArchiveFile(zip_file, stat.size, stat.name); } u32 ZipArchiveFile::Read(void* buffer, u32 length) @@ -104,5 +104,15 @@ ArchiveFile *ZipArchive::OpenFirstFile() return nullptr; zip_stat_t stat; zip_stat_index(zip, 0, 0, &stat); - return new ZipArchiveFile(zipFile, stat.size); + return new ZipArchiveFile(zipFile, stat.size, stat.name); +} + +ArchiveFile *ZipArchive::OpenFileByIndex(size_t index) +{ + zip_file_t *zipFile = zip_fopen_index(zip, index, 0); + if (zipFile == nullptr) + return nullptr; + zip_stat_t stat; + zip_stat_index(zip, index, 0, &stat); + return new ZipArchiveFile(zipFile, stat.size, stat.name); } diff --git a/core/archive/ZipArchive.h b/core/archive/ZipArchive.h index 1369a5c48..8b475ab78 100644 --- a/core/archive/ZipArchive.h +++ b/core/archive/ZipArchive.h @@ -31,11 +31,11 @@ public: ArchiveFile* OpenFile(const char* name) override; ArchiveFile* OpenFileByCrc(u32 crc) override; - bool Open(const void *data, size_t size); - ArchiveFile *OpenFirstFile(); - -protected: bool Open(FILE *file) override; + bool Open(const void *data, size_t size); + + ArchiveFile *OpenFirstFile(); + ArchiveFile *OpenFileByIndex(size_t index); private: zip_t *zip = nullptr; @@ -44,8 +44,8 @@ private: class ZipArchiveFile : public ArchiveFile { public: - ZipArchiveFile(zip_file_t *zip_file, size_t length) - : zip_file(zip_file), _length(length) {} + ZipArchiveFile(zip_file_t *zip_file, size_t length, const char *name) + : zip_file(zip_file), _length(length), name(name) {} ~ZipArchiveFile() override { zip_fclose(zip_file); } @@ -53,8 +53,12 @@ public: size_t length() override { return _length; } + const char *getName() override { + return name; + } private: zip_file_t *zip_file; size_t _length; + const char *name; }; diff --git a/core/archive/archive.h b/core/archive/archive.h index 720613f07..49e925e0c 100644 --- a/core/archive/archive.h +++ b/core/archive/archive.h @@ -28,6 +28,7 @@ public: virtual ~ArchiveFile() = default; virtual u32 Read(void *buffer, u32 length) = 0; virtual size_t length() = 0; + virtual const char *getName() { return nullptr; } }; class Archive diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index 24aec35e5..20720d33f 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -108,6 +108,7 @@ Option PerPixelLayers("rend.PerPixelLayers", 32); Option NativeDepthInterpolation("rend.NativeDepthInterpolation", false); Option EmulateFramebuffer("rend.EmulateFramebuffer", false); Option FixUpscaleBleedingEdge("rend.FixUpscaleBleedingEdge", true); +Option CustomGpuDriver("rend.CustomGpuDriver", false); #ifdef VIDEO_ROUTING Option VideoRouting("rend.VideoRouting", false); Option VideoRoutingScale("rend.VideoRoutingScale", false); diff --git a/core/cfg/option.h b/core/cfg/option.h index 50f80bf17..05d30b6f2 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -474,6 +474,7 @@ extern Option DupeFrames; extern Option NativeDepthInterpolation; extern Option EmulateFramebuffer; extern Option FixUpscaleBleedingEdge; +extern Option CustomGpuDriver; #ifdef VIDEO_ROUTING extern Option VideoRouting; extern Option VideoRoutingScale; diff --git a/core/deps/libadrenotools b/core/deps/libadrenotools new file mode 160000 index 000000000..5deac9f1a --- /dev/null +++ b/core/deps/libadrenotools @@ -0,0 +1 @@ +Subproject commit 5deac9f1ab2bd833ad664bc3386ac1e8998cecb3 diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 77927f6b5..cd009ab8a 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -51,6 +51,9 @@ #ifdef __ANDROID__ #include "gui_android.h" +#if HOST_CPU == CPU_ARM64 && USE_VULKAN +#include "rend/vulkan/adreno.h" +#endif #endif #ifdef _WIN32 @@ -2762,6 +2765,75 @@ static void gui_display_settings() ImGui::Text("Driver Name: %s", GraphicsContext::Instance()->getDriverName().c_str()); ImGui::Text("Version: %s", GraphicsContext::Instance()->getDriverVersion().c_str()); +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 && USE_VULKAN + if (isVulkan(config::RendererType)) + { + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); + if (config::CustomGpuDriver) + { + std::string name, description, vendor, version; + if (getCustomGpuDriverInfo(name, description, vendor, version)) + { + ImGui::Text("Custom Driver:"); + ImGui::Indent(); + ImGui::Text("%s - %s", name.c_str(), description.c_str()); + ImGui::Text("%s - %s", vendor.c_str(), version.c_str()); + ImGui::Unindent(); + } + + if (ImGui::Button("Use Default Driver")) { + config::CustomGpuDriver = false; + ImGui::OpenPopup("Reset Vulkan"); + } + } + else if (ImGui::Button("Upload Custom Driver")) + ImGui::OpenPopup("Select custom GPU driver"); + + static bool driverDirty; + const auto& callback = [](bool cancelled, std::string selection) { + if (!cancelled) { + try { + uploadCustomGpuDriver(selection); + config::CustomGpuDriver = true; + driverDirty = true; + } catch (const FlycastException& e) { + gui_error(e.what()); + config::CustomGpuDriver = false; + } + } + return true; + }; + select_file_popup("Select custom GPU driver", callback, true, "zip"); + + if (driverDirty) { + ImGui::OpenPopup("Reset Vulkan"); + driverDirty = false; + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ScaledVec2(20, 20)); + if (ImGui::BeginPopupModal("Reset Vulkan", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) + { + ImGui::Text("Do you want to reset Vulkan to use new driver?"); + ImGui::NewLine(); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20 * settings.display.uiScale, ImGui::GetStyle().ItemSpacing.y)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); + if (ImGui::Button("Yes")) + { + mainui_reinit(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No")) + ImGui::CloseCurrentPopup(); + ImGui::PopStyleVar(2); + + ImGui::EndPopup(); + } + ImGui::PopStyleVar(); + + ImGui::PopStyleVar(); + } +#endif ImGui::PopStyleVar(); ImGui::EndTabItem(); } diff --git a/core/rend/vulkan/adreno.cpp b/core/rend/vulkan/adreno.cpp new file mode 100644 index 000000000..928964a48 --- /dev/null +++ b/core/rend/vulkan/adreno.cpp @@ -0,0 +1,209 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#include "build.h" +#if defined(__ANDROID__) && !defined(LIBRETRO) && HOST_CPU == CPU_ARM64 +#include "adreno.h" +#include +#include "cfg/option.h" +#include +#include "json.hpp" +using namespace nlohmann; +#include "archive/ZipArchive.h" +#include "oslib/directory.h" +#include "stdclass.h" + +std::string getNativeLibraryPath(); +std::string getFilesPath(); + +const std::string DRIVER_PATH = "/gpu_driver/"; +static void *libvulkanHandle; + +static json loadDriverMeta() +{ + std::string fullPath = getFilesPath() + DRIVER_PATH + "meta.json"; + FILE *f = nowide::fopen(fullPath.c_str(), "rt"); + if (f == nullptr) { + WARN_LOG(RENDERER, "Can't open %s", fullPath.c_str()); + return json{}; + } + std::string content(4096, '\0'); + size_t l = fread(content.data(), 1, content.size(), f); + fclose(f); + if (l <= 0) { + WARN_LOG(RENDERER, "Can't read %s", fullPath.c_str()); + return json{}; + } + content.resize(l); + try { + return json::parse(content); + } catch (const json::exception& e) { + WARN_LOG(COMMON, "Corrupted meta.json file: %s", e.what()); + return json{}; + } +} + +static std::string getLibraryName() +{ + json v = loadDriverMeta(); + std::string name; + try { + v.at("libraryName").get_to(name); + } catch (const json::exception& e) { + } + return name; +} + +PFN_vkGetInstanceProcAddr loadVulkanDriver() +{ + // If the user has selected a custom driver, try to load it + if (config::CustomGpuDriver) + { + std::string libName = getLibraryName(); + if (!libName.empty()) + { + std::string driverPath = getFilesPath() + DRIVER_PATH; + std::string tmpLibDir = getFilesPath() + "/tmp/"; + mkdir(tmpLibDir.c_str(), 0755); + //std::string redirectDir = get_writable_data_path(""); + libvulkanHandle = adrenotools_open_libvulkan( + RTLD_NOW | RTLD_LOCAL, + ADRENOTOOLS_DRIVER_CUSTOM /* | ADRENOTOOLS_DRIVER_FILE_REDIRECT */, + tmpLibDir.c_str(), + getNativeLibraryPath().c_str(), + driverPath.c_str(), + libName.c_str(), + nullptr, //redirectDir.c_str(), + nullptr); + if (libvulkanHandle == nullptr) { + char *error = dlerror(); + WARN_LOG(RENDERER, "Failed to load custom Vulkan driver %s%s: %s", driverPath.c_str(), libName.c_str(), error ? error : ""); + } + } + } + if (libvulkanHandle == nullptr) + { + libvulkanHandle = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL); + if (libvulkanHandle == nullptr) + { + char *error = dlerror(); + WARN_LOG(RENDERER, "Failed to load system Vulkan driver: %s", error ? error : ""); + return nullptr; + } + } + + return reinterpret_cast(dlsym(libvulkanHandle, "vkGetInstanceProcAddr")); +} + +void unloadVulkanDriver() +{ + if (libvulkanHandle != nullptr) { + dlclose(libvulkanHandle); + libvulkanHandle = nullptr; + } +} + +bool getCustomGpuDriverInfo(std::string& name, std::string& description, std::string& vendor, std::string& version) +{ + json j = loadDriverMeta(); + try { + j.at("name").get_to(name); + } catch (const json::exception& e) { + return false; + } + try { + j.at("description").get_to(description); + } catch (const json::exception& e) { + description = ""; + } + try { + j.at("vendor").get_to(vendor); + } catch (const json::exception& e) { + vendor = ""; + } + try { + j.at("driverVersion").get_to(version); + } catch (const json::exception& e) { + version = ""; + } + + return true; +} + +void uploadCustomGpuDriver(const std::string& zipPath) +{ + FILE *zipf = nowide::fopen(zipPath.c_str(), "rb"); + if (zipf == nullptr) + throw FlycastException("Can't open zip file"); + ZipArchive archive; + if (!archive.Open(zipf)) + throw FlycastException("Invalid zip file"); + std::string fullPath = getFilesPath() + DRIVER_PATH; + flycast::mkdir(fullPath.c_str(), 0755); + // Clean driver directory + DIR *dir = flycast::opendir(fullPath.c_str()); + if (dir != nullptr) + { + while (true) + { + dirent *direntry = flycast::readdir(dir); + if (direntry == nullptr) + break; + std::string name = direntry->d_name; + if (name == "." || name == "..") + continue; + name = fullPath + name; + unlink(name.c_str()); + } + } + // Extract and save files + for (size_t i = 0; ; i++) + { + ArchiveFile *afile = archive.OpenFileByIndex(i); + if (afile == nullptr) + break; + FILE *f = fopen((fullPath + afile->getName()).c_str(), "wb"); + if (f == nullptr) { + delete afile; + throw FlycastException("Can't save files"); + } + u8 buf[8_KB]; + while (true) + { + u32 len = afile->Read(buf, sizeof(buf)); + if (len < 0) + { + fclose(f); + delete afile; + throw FlycastException("Can't read zip"); + } + if (len == 0) + break; + if (fwrite(buf, 1, len, f) != len) + { + fclose(f); + delete afile; + throw FlycastException("Can't save files"); + } + } + fclose(f); + delete afile; + } +} + +#endif // __ANDROID__ && !LIBRETRO && arm64 diff --git a/core/rend/vulkan/adreno.h b/core/rend/vulkan/adreno.h new file mode 100644 index 000000000..261b0af8c --- /dev/null +++ b/core/rend/vulkan/adreno.h @@ -0,0 +1,25 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#pragma once +#include "vulkan.h" + +PFN_vkGetInstanceProcAddr loadVulkanDriver(); +void unloadVulkanDriver(); +bool getCustomGpuDriverInfo(std::string& name, std::string& description, std::string& vendor, std::string& version); +void uploadCustomGpuDriver(const std::string& zipPath); diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index a46d48ca7..48d4b61eb 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -33,6 +33,9 @@ #include "oslib/oslib.h" #include "vulkan_driver.h" #include "rend/transform_matrix.h" +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 +#include "adreno.h" +#endif #if VULKAN_HPP_DISPATCH_LOADER_DYNAMIC == 1 VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE @@ -139,8 +142,13 @@ bool VulkanContext::InitInstance(const char** extensions, uint32_t extensions_co try { #if VULKAN_HPP_DISPATCH_LOADER_DYNAMIC == 1 + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = nullptr; +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 + vkGetInstanceProcAddr = loadVulkanDriver(); +#else static vk::DynamicLoader dl; - PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = dl.getProcAddress("vkGetInstanceProcAddr"); + vkGetInstanceProcAddr = dl.getProcAddress("vkGetInstanceProcAddr"); +#endif if (vkGetInstanceProcAddr == nullptr) { ERROR_LOG(RENDERER, "Vulkan entry point vkGetInstanceProcAddr not found"); return false; @@ -1042,6 +1050,9 @@ void VulkanContext::term() #endif #endif instance.reset(); +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 + unloadVulkanDriver(); +#endif } void VulkanContext::DoSwapAutomation() diff --git a/shell/android-studio/flycast/build.gradle b/shell/android-studio/flycast/build.gradle index 7bd6c0390..5d0e455ba 100644 --- a/shell/android-studio/flycast/build.gradle +++ b/shell/android-studio/flycast/build.gradle @@ -73,6 +73,10 @@ android { excludes += ['META-INF/DEPENDENCIES'] } } + packaging { + // This is necessary for libadrenotools custom driver loading + jniLibs.useLegacyPackaging = true + } } dependencies { diff --git a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java index 140cdc9b6..624714741 100644 --- a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java +++ b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/BaseGLActivity.java @@ -415,4 +415,12 @@ public abstract class BaseGLActivity extends Activity implements ActivityCompat. } private static native void register(BaseGLActivity activity); + + public String getNativeLibDir() { + return getApplicationContext().getApplicationInfo().nativeLibraryDir; + } + + public String getInternalFilesDir() { + return getFilesDir().getAbsolutePath(); + } } diff --git a/shell/android-studio/flycast/src/main/jni/src/Android.cpp b/shell/android-studio/flycast/src/main/jni/src/Android.cpp index 0692c7c9a..bb88ef268 100644 --- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp @@ -653,3 +653,19 @@ extern "C" void abort_message(const char* format, ...) ERROR_LOG(BOOT, "%s", buffer); abort(); } + +std::string getNativeLibraryPath() +{ + JNIEnv *env = jni::env(); + jmethodID getNativeLibDir = env->GetMethodID(env->GetObjectClass(g_activity), "getNativeLibDir", "()Ljava/lang/String;"); + jni::String nativeLibDir(jni::env()->CallObjectMethod(g_activity, getNativeLibDir)); + return nativeLibDir; +} + +std::string getFilesPath() +{ + JNIEnv *env = jni::env(); + jmethodID getInternalFilesDir = env->GetMethodID(env->GetObjectClass(g_activity), "getInternalFilesDir", "()Ljava/lang/String;"); + jni::String filesDir(jni::env()->CallObjectMethod(g_activity, getInternalFilesDir)); + return filesDir; +} From da6c34b2fd5373ad90881be827eb0a79e03050a2 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 7 Apr 2024 12:34:34 +0200 Subject: [PATCH 35/86] android workflow: fetch submodules recursively (for libadrenotools) --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 03bf4ca06..09391757a 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - submodules: true + submodules: recursive - uses: actions/setup-java@v4 with: From 24db1422b8bfa6e5ffb2d63a90230e59094afa11 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 8 Apr 2024 19:07:44 +0200 Subject: [PATCH 36/86] ui: rearrange Settings > Video screen Issue #665 --- core/rend/gui.cpp | 313 ++++++++++++++++++++++++---------------------- 1 file changed, 161 insertions(+), 152 deletions(-) diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index cd009ab8a..c1ef2d66a 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -2002,9 +2002,55 @@ static void gui_display_settings() } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); - const bool has_per_pixel = GraphicsContext::Instance()->hasPerPixel(); + + constexpr int apiCount = 0 + #ifdef USE_VULKAN + + 1 + #endif + #ifdef USE_DX9 + + 1 + #endif + #ifdef USE_OPENGL + + 1 + #endif + #ifdef USE_DX11 + + 1 + #endif + ; + + if (apiCount > 1) + { + header("Graphics API"); + { + ImGui::Columns(apiCount, "renderApi", false); +#ifdef USE_OPENGL + ImGui::RadioButton("OpenGL", &renderApi, 0); + ImGui::NextColumn(); +#endif +#ifdef USE_VULKAN +#ifdef __APPLE__ + ImGui::RadioButton("Vulkan (Metal)", &renderApi, 1); + ImGui::SameLine(0, style.ItemInnerSpacing.x); + ShowHelpMarker("MoltenVK: An implementation of Vulkan that runs on Apple's Metal graphics framework"); +#else + ImGui::RadioButton("Vulkan", &renderApi, 1); +#endif // __APPLE__ + ImGui::NextColumn(); +#endif +#ifdef USE_DX9 + ImGui::RadioButton("DirectX 9", &renderApi, 2); + ImGui::NextColumn(); +#endif +#ifdef USE_DX11 + ImGui::RadioButton("DirectX 11", &renderApi, 3); + ImGui::NextColumn(); +#endif + ImGui::Columns(1, nullptr, false); + } + } header("Transparent Sorting"); { + const bool has_per_pixel = GraphicsContext::Instance()->hasPerPixel(); int renderer = perPixel ? 2 : config::PerStripSorting ? 1 : 0; ImGui::Columns(has_per_pixel ? 3 : 2, "renderers", false); ImGui::RadioButton("Per Triangle", &renderer, 0); @@ -2043,151 +2089,6 @@ static void gui_display_settings() header("Rendering Options"); { - ImGui::Text("Automatic Frame Skipping:"); - ImGui::Columns(3, "autoskip", false); - OptionRadioButton("Disabled", config::AutoSkipFrame, 0, "No frame skipping"); - ImGui::NextColumn(); - OptionRadioButton("Normal", config::AutoSkipFrame, 1, "Skip a frame when the GPU and CPU are both running slow"); - ImGui::NextColumn(); - OptionRadioButton("Maximum", config::AutoSkipFrame, 2, "Skip a frame when the GPU is running slow"); - ImGui::Columns(1, nullptr, false); - - OptionCheckbox("Shadows", config::ModifierVolumes, - "Enable modifier volumes, usually used for shadows"); - OptionCheckbox("Fog", config::Fog, "Enable fog effects"); - OptionCheckbox("Widescreen", config::Widescreen, - "Draw geometry outside of the normal 4:3 aspect ratio. May produce graphical glitches in the revealed areas.\nAspect Fit and shows the full 16:9 content."); - { - DisabledScope scope(!config::Widescreen); - - ImGui::Indent(); - OptionCheckbox("Super Widescreen", config::SuperWidescreen, - "Use the full width of the screen or window when its aspect ratio is greater than 16:9.\nAspect Fill and remove black bars."); - ImGui::Unindent(); - } - OptionCheckbox("Widescreen Game Cheats", config::WidescreenGameHacks, - "Modify the game so that it displays in 16:9 anamorphic format and use horizontal screen stretching. Only some games are supported."); - - const std::array aniso{ 1, 2, 4, 8, 16 }; - const std::array anisoText{ "Disabled", "2x", "4x", "8x", "16x" }; - u32 afSelected = 0; - for (u32 i = 0; i < aniso.size(); i++) - { - if (aniso[i] == config::AnisotropicFiltering) - afSelected = i; - } - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); - if (ImGui::BeginCombo("##Anisotropic Filtering", anisoText[afSelected].c_str(), ImGuiComboFlags_NoArrowButton)) - { - for (u32 i = 0; i < aniso.size(); i++) - { - bool is_selected = aniso[i] == config::AnisotropicFiltering; - if (ImGui::Selectable(anisoText[i].c_str(), is_selected)) - config::AnisotropicFiltering = aniso[i]; - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); - ImGui::SameLine(0, innerSpacing); - - if (ImGui::ArrowButton("##Decrease Anisotropic Filtering", ImGuiDir_Left)) - { - if (afSelected > 0) - config::AnisotropicFiltering = aniso[afSelected - 1]; - } - ImGui::SameLine(0, innerSpacing); - if (ImGui::ArrowButton("##Increase Anisotropic Filtering", ImGuiDir_Right)) - { - if (afSelected < aniso.size() - 1) - config::AnisotropicFiltering = aniso[afSelected + 1]; - } - ImGui::SameLine(0, style.ItemInnerSpacing.x); - - ImGui::Text("Anisotropic Filtering"); - ImGui::SameLine(); - ShowHelpMarker("Higher values make textures viewed at oblique angles look sharper, but are more demanding on the GPU. This option only has a visible impact on mipmapped textures."); - - ImGui::Text("Texture Filtering:"); - ImGui::Columns(3, "textureFiltering", false); - OptionRadioButton("Default", config::TextureFiltering, 0, "Use the game's default texture filtering"); - ImGui::NextColumn(); - OptionRadioButton("Force Nearest-Neighbor", config::TextureFiltering, 1, "Force nearest-neighbor filtering for all textures. Crisper appearance, but may cause various rendering issues. This option usually does not affect performance."); - ImGui::NextColumn(); - OptionRadioButton("Force Linear", config::TextureFiltering, 2, "Force linear filtering for all textures. Smoother appearance, but may cause various rendering issues. This option usually does not affect performance."); - ImGui::Columns(1, nullptr, false); - -#ifndef TARGET_IPHONE - OptionCheckbox("VSync", config::VSync, "Synchronizes the frame rate with the screen refresh rate. Recommended"); - if (isVulkan(config::RendererType)) - { - ImGui::Indent(); - { - DisabledScope scope(!config::VSync); - - OptionCheckbox("Duplicate frames", config::DupeFrames, "Duplicate frames on high refresh rate monitors (120 Hz and higher)"); - } - ImGui::Unindent(); - } -#endif - OptionCheckbox("Show FPS Counter", config::ShowFPS, "Show on-screen frame/sec counter"); - OptionCheckbox("Show VMU In-game", config::FloatVMUs, "Show the VMU LCD screens while in-game"); - OptionCheckbox("Rotate Screen 90°", config::Rotate90, "Rotate the screen 90° counterclockwise"); - OptionCheckbox("Delay Frame Swapping", config::DelayFrameSwapping, - "Useful to avoid flashing screen or glitchy videos. Not recommended on slow platforms"); - OptionCheckbox("Fix Upscale Bleeding Edge", config::FixUpscaleBleedingEdge, - "Helps with texture bleeding case when upscaling. Disabling it can help if pixels are warping when upscaling in 2D games (MVC2, CVS, KOF, etc.)"); - OptionCheckbox("Native Depth Interpolation", config::NativeDepthInterpolation, - "Helps with texture corruption and depth issues on AMD GPUs. Can also help Intel GPUs in some cases."); - OptionCheckbox("Full Framebuffer Emulation", config::EmulateFramebuffer, - "Fully accurate VRAM framebuffer emulation. Helps games that directly access the framebuffer for special effects. " - "Very slow and incompatible with upscaling and wide screen."); - constexpr int apiCount = 0 - #ifdef USE_VULKAN - + 1 - #endif - #ifdef USE_DX9 - + 1 - #endif - #ifdef USE_OPENGL - + 1 - #endif - #ifdef USE_DX11 - + 1 - #endif - ; - - if (apiCount > 1) - { - ImGui::Text("Graphics API:"); - ImGui::Columns(apiCount, "renderApi", false); -#ifdef USE_OPENGL - ImGui::RadioButton("OpenGL", &renderApi, 0); - ImGui::NextColumn(); -#endif -#ifdef USE_VULKAN -#ifdef __APPLE__ - ImGui::RadioButton("Vulkan (Metal)", &renderApi, 1); - ImGui::SameLine(0, style.ItemInnerSpacing.x); - ShowHelpMarker("MoltenVK: An implementation of Vulkan that runs on Apple's Metal graphics framework"); -#else - ImGui::RadioButton("Vulkan", &renderApi, 1); -#endif // __APPLE__ - ImGui::NextColumn(); -#endif -#ifdef USE_DX9 - ImGui::RadioButton("DirectX 9", &renderApi, 2); - ImGui::NextColumn(); -#endif -#ifdef USE_DX11 - ImGui::RadioButton("DirectX 11", &renderApi, 3); - ImGui::NextColumn(); -#endif - ImGui::Columns(1, nullptr, false); - } - const std::array scalings{ 0.5f, 1.f, 1.5f, 2.f, 2.5f, 3.f, 4.f, 4.5f, 5.f, 6.f, 7.f, 8.f, 9.f }; const std::array scalingsText{ "Half", "Native", "x1.5", "x2", "x2.5", "x3", "x4", "x4.5", "x5", "x6", "x7", "x8", "x9" }; std::array vres; @@ -2238,10 +2139,44 @@ static void gui_display_settings() ImGui::SameLine(); ShowHelpMarker("Internal render resolution. Higher is better, but more demanding on the GPU. Values higher than your display resolution (but no more than double your display resolution) can be used for supersampling, which provides high-quality antialiasing without reducing sharpness."); +#ifndef TARGET_IPHONE + OptionCheckbox("VSync", config::VSync, "Synchronizes the frame rate with the screen refresh rate. Recommended"); + if (isVulkan(config::RendererType)) + { + ImGui::Indent(); + { + DisabledScope scope(!config::VSync); + + OptionCheckbox("Duplicate frames", config::DupeFrames, "Duplicate frames on high refresh rate monitors (120 Hz and higher)"); + } + ImGui::Unindent(); + } +#endif + OptionCheckbox("Show VMU In-game", config::FloatVMUs, "Show the VMU LCD screens while in-game"); + OptionCheckbox("Full Framebuffer Emulation", config::EmulateFramebuffer, + "Fully accurate VRAM framebuffer emulation. Helps games that directly access the framebuffer for special effects. " + "Very slow and incompatible with upscaling and wide screen."); + OptionCheckbox("Load Custom Textures", config::CustomTextures, + "Load custom/high-res textures from data/textures/"); + } + ImGui::Spacing(); + header("Aspect Ratio"); + { + OptionCheckbox("Widescreen", config::Widescreen, + "Draw geometry outside of the normal 4:3 aspect ratio. May produce graphical glitches in the revealed areas.\nAspect Fit and shows the full 16:9 content."); + { + DisabledScope scope(!config::Widescreen); + + ImGui::Indent(); + OptionCheckbox("Super Widescreen", config::SuperWidescreen, + "Use the full width of the screen or window when its aspect ratio is greater than 16:9.\nAspect Fill and remove black bars."); + ImGui::Unindent(); + } + OptionCheckbox("Widescreen Game Cheats", config::WidescreenGameHacks, + "Modify the game so that it displays in 16:9 anamorphic format and use horizontal screen stretching. Only some games are supported."); OptionSlider("Horizontal Stretching", config::ScreenStretching, 100, 250, "Stretch the screen horizontally", "%d%%"); - OptionArrowButtons("Frame Skipping", config::SkipFrame, 0, 6, - "Number of frames to skip between two actually rendered frames"); + OptionCheckbox("Rotate Screen 90°", config::Rotate90, "Rotate the screen 90° counterclockwise"); } if (perPixel) { @@ -2295,10 +2230,86 @@ static void gui_display_settings() "Maximum number of transparent layers. May need to be increased for some complex scenes. Decreasing it may improve performance."); } ImGui::Spacing(); - header("Render to Texture"); + header("Performance"); { - OptionCheckbox("Copy to VRAM", config::RenderToTextureBuffer, + ImGui::Text("Automatic Frame Skipping:"); + ImGui::Columns(3, "autoskip", false); + OptionRadioButton("Disabled", config::AutoSkipFrame, 0, "No frame skipping"); + ImGui::NextColumn(); + OptionRadioButton("Normal", config::AutoSkipFrame, 1, "Skip a frame when the GPU and CPU are both running slow"); + ImGui::NextColumn(); + OptionRadioButton("Maximum", config::AutoSkipFrame, 2, "Skip a frame when the GPU is running slow"); + ImGui::Columns(1, nullptr, false); + + OptionArrowButtons("Frame Skipping", config::SkipFrame, 0, 6, + "Number of frames to skip between two actually rendered frames"); + OptionCheckbox("Shadows", config::ModifierVolumes, + "Enable modifier volumes, usually used for shadows"); + OptionCheckbox("Fog", config::Fog, "Enable fog effects"); + } + ImGui::Spacing(); + header("Advanced"); + { + OptionCheckbox("Delay Frame Swapping", config::DelayFrameSwapping, + "Useful to avoid flashing screen or glitchy videos. Not recommended on slow platforms"); + OptionCheckbox("Fix Upscale Bleeding Edge", config::FixUpscaleBleedingEdge, + "Helps with texture bleeding case when upscaling. Disabling it can help if pixels are warping when upscaling in 2D games (MVC2, CVS, KOF, etc.)"); + OptionCheckbox("Native Depth Interpolation", config::NativeDepthInterpolation, + "Helps with texture corruption and depth issues on AMD GPUs. Can also help Intel GPUs in some cases."); + OptionCheckbox("Copy Rendered Textures to VRAM", config::RenderToTextureBuffer, "Copy rendered-to textures back to VRAM. Slower but accurate"); + const std::array aniso{ 1, 2, 4, 8, 16 }; + const std::array anisoText{ "Disabled", "2x", "4x", "8x", "16x" }; + u32 afSelected = 0; + for (u32 i = 0; i < aniso.size(); i++) + { + if (aniso[i] == config::AnisotropicFiltering) + afSelected = i; + } + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); + if (ImGui::BeginCombo("##Anisotropic Filtering", anisoText[afSelected].c_str(), ImGuiComboFlags_NoArrowButton)) + { + for (u32 i = 0; i < aniso.size(); i++) + { + bool is_selected = aniso[i] == config::AnisotropicFiltering; + if (ImGui::Selectable(anisoText[i].c_str(), is_selected)) + config::AnisotropicFiltering = aniso[i]; + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(0, innerSpacing); + + if (ImGui::ArrowButton("##Decrease Anisotropic Filtering", ImGuiDir_Left)) + { + if (afSelected > 0) + config::AnisotropicFiltering = aniso[afSelected - 1]; + } + ImGui::SameLine(0, innerSpacing); + if (ImGui::ArrowButton("##Increase Anisotropic Filtering", ImGuiDir_Right)) + { + if (afSelected < aniso.size() - 1) + config::AnisotropicFiltering = aniso[afSelected + 1]; + } + ImGui::SameLine(0, style.ItemInnerSpacing.x); + + ImGui::Text("Anisotropic Filtering"); + ImGui::SameLine(); + ShowHelpMarker("Higher values make textures viewed at oblique angles look sharper, but are more demanding on the GPU. This option only has a visible impact on mipmapped textures."); + + ImGui::Text("Texture Filtering:"); + ImGui::Columns(3, "textureFiltering", false); + OptionRadioButton("Default", config::TextureFiltering, 0, "Use the game's default texture filtering"); + ImGui::NextColumn(); + OptionRadioButton("Force Nearest-Neighbor", config::TextureFiltering, 1, "Force nearest-neighbor filtering for all textures. Crisper appearance, but may cause various rendering issues. This option usually does not affect performance."); + ImGui::NextColumn(); + OptionRadioButton("Force Linear", config::TextureFiltering, 2, "Force linear filtering for all textures. Smoother appearance, but may cause various rendering issues. This option usually does not affect performance."); + ImGui::Columns(1, nullptr, false); + + OptionCheckbox("Show FPS Counter", config::ShowFPS, "Show on-screen frame/sec counter"); } ImGui::Spacing(); header("Texture Upscaling"); @@ -2311,8 +2322,6 @@ static void gui_display_settings() OptionArrowButtons("Max Threads", config::MaxThreads, 1, 8, "Maximum number of threads to use for texture upscaling. Recommended: number of physical cores minus one"); #endif - OptionCheckbox("Load Custom Textures", config::CustomTextures, - "Load custom/high-res textures from data/textures/"); } #ifdef VIDEO_ROUTING #ifdef __APPLE__ From bf249061cf3d4e7e4bd3745a480406e7ff8654cd Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 8 Apr 2024 19:09:14 +0200 Subject: [PATCH 37/86] thegamedb: ignore common disk ids T0000 and T0000M These are default disk ids and shouldn't be used to identify media --- core/rend/boxart/gamesdb.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/rend/boxart/gamesdb.cpp b/core/rend/boxart/gamesdb.cpp index 6fd20a83d..063c3587e 100644 --- a/core/rend/boxart/gamesdb.cpp +++ b/core/rend/boxart/gamesdb.cpp @@ -323,7 +323,8 @@ void TheGamesDb::scrape(GameBoxart& item) return; fetchPlatforms(); - if (!item.uniqueId.empty()) + // Ignore default disk ids used by kos and katana + if (!item.uniqueId.empty() && item.uniqueId != "T0000" && item.uniqueId != "T0000M") { std::string url = makeUrl("Games/ByGameUniqueID") + "&fields=overview,uids&include=boxart&filter%5Bplatform%5D=" + std::to_string(dreamcastPlatformId) + "&uid=" + http::urlEncode(item.uniqueId); From 010fba15a8c8ea72c99201b2b14e781a0df9d0f2 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 8 Apr 2024 21:26:18 +0200 Subject: [PATCH 38/86] macOS build fix --- core/rend/gui.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index c1ef2d66a..ea07d6cf2 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -2018,6 +2018,7 @@ static void gui_display_settings() #endif ; + float innerSpacing = ImGui::GetStyle().ItemInnerSpacing.x; if (apiCount > 1) { header("Graphics API"); @@ -2030,7 +2031,7 @@ static void gui_display_settings() #ifdef USE_VULKAN #ifdef __APPLE__ ImGui::RadioButton("Vulkan (Metal)", &renderApi, 1); - ImGui::SameLine(0, style.ItemInnerSpacing.x); + ImGui::SameLine(0, innerSpacing); ShowHelpMarker("MoltenVK: An implementation of Vulkan that runs on Apple's Metal graphics framework"); #else ImGui::RadioButton("Vulkan", &renderApi, 1); @@ -2084,8 +2085,6 @@ static void gui_display_settings() } } ImGui::Spacing(); - ImGuiStyle& style = ImGui::GetStyle(); - float innerSpacing = style.ItemInnerSpacing.x; header("Rendering Options"); { @@ -2133,7 +2132,7 @@ static void gui_display_settings() if (selected < vres.size() - 1) config::RenderResolution = vres[selected + 1]; } - ImGui::SameLine(0, style.ItemInnerSpacing.x); + ImGui::SameLine(0, innerSpacing); ImGui::Text("Internal Resolution"); ImGui::SameLine(); @@ -2220,7 +2219,7 @@ static void gui_display_settings() if (selected < bufSizes.size() - 1) config::PixelBufferSize = bufSizes[selected + 1]; } - ImGui::SameLine(0, style.ItemInnerSpacing.x); + ImGui::SameLine(0, innerSpacing); ImGui::Text("Pixel Buffer Size"); ImGui::SameLine(); @@ -2294,7 +2293,7 @@ static void gui_display_settings() if (afSelected < aniso.size() - 1) config::AnisotropicFiltering = aniso[afSelected + 1]; } - ImGui::SameLine(0, style.ItemInnerSpacing.x); + ImGui::SameLine(0, innerSpacing); ImGui::Text("Anisotropic Filtering"); ImGui::SameLine(); From 42e98e86d06a4c3556eb6a13cdd3f5ef4c7fa9c0 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 10 Apr 2024 17:08:24 +0200 Subject: [PATCH 39/86] set thread names Name all threads to help debugging and improve crash reports (windows only) Add ^C handler for clean exit on linux --- CMakeLists.txt | 2 ++ core/audio/audiobackend_directsound.cpp | 2 ++ core/debug/gdb_server.cpp | 2 ++ core/emulator.cpp | 1 + core/linux/common.cpp | 34 ++++++++++++++++++- core/network/ggpo.cpp | 1 + core/network/naomi_network.h | 2 ++ core/network/picoppp.cpp | 4 ++- core/oslib/oslib.h | 14 ++++++++ core/rend/CustomTexture.h | 2 +- core/rend/boxart/boxart.cpp | 2 ++ core/rend/game_scanner.h | 2 ++ core/rend/mainui.cpp | 1 + core/stdclass.cpp | 5 ++- core/stdclass.h | 5 +-- core/windows/winmain.cpp | 18 ++++++++++ .../flycast/src/main/jni/src/Android.cpp | 4 +-- shell/apple/common/util.mm | 27 +++++++++++++++ shell/switch/ucontext.h | 2 +- 19 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 shell/apple/common/util.mm diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bf2241af..49982f6bc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1569,6 +1569,7 @@ if(NOT LIBRETRO) target_sources(${PROJECT_NAME} PRIVATE shell/apple/common/http_client.mm + shell/apple/common/util.mm shell/apple/emulator-ios/emulator/AppDelegate.h shell/apple/emulator-ios/emulator/AppDelegate.mm shell/apple/emulator-ios/emulator/ios_main.mm @@ -1664,6 +1665,7 @@ if(NOT LIBRETRO) target_sources(${PROJECT_NAME} PRIVATE shell/apple/common/http_client.mm + shell/apple/common/util.mm shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.h shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm shell/apple/emulator-osx/emulator-osx/osx-main.mm) diff --git a/core/audio/audiobackend_directsound.cpp b/core/audio/audiobackend_directsound.cpp index ae81884b7..726f785e0 100644 --- a/core/audio/audiobackend_directsound.cpp +++ b/core/audio/audiobackend_directsound.cpp @@ -9,6 +9,7 @@ #include #include "stdclass.h" #include "windows/comptr.h" +#include "oslib/oslib.h" HWND getNativeHwnd(); #define verifyc(x) verify(!FAILED(x)) @@ -46,6 +47,7 @@ class DirectSoundBackend : public AudioBackend void audioThreadMain() { + ThreadName _("FlyDirectSound"); audioThreadRunning = true; while (true) { diff --git a/core/debug/gdb_server.cpp b/core/debug/gdb_server.cpp index eb8cb2f7c..4141a7d20 100644 --- a/core/debug/gdb_server.cpp +++ b/core/debug/gdb_server.cpp @@ -23,6 +23,7 @@ #include "debug_agent.h" #include "network/net_platform.h" #include "cfg/option.h" +#include "oslib/oslib.h" #include #include #include @@ -140,6 +141,7 @@ private: void serverThread() { + ThreadName _("GdbServer"); while (!stopRequested) { fd_set fds; diff --git a/core/emulator.cpp b/core/emulator.cpp index bc69db3b9..03da7a54c 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -850,6 +850,7 @@ void Emulator::start() { const std::lock_guard lock(mutex); threadResult = std::async(std::launch::async, [this] { + ThreadName _("Flycast-emu"); InitAudio(); try { diff --git a/core/linux/common.cpp b/core/linux/common.cpp index d3b2651d3..37c5151ca 100644 --- a/core/linux/common.cpp +++ b/core/linux/common.cpp @@ -15,6 +15,9 @@ #include #endif #include +#ifdef __linux__ +#include +#endif #include "oslib/host_context.h" @@ -22,6 +25,7 @@ #include "rend/TexCache.h" #include "hw/mem/addrspace.h" #include "hw/mem/mem_watch.h" +#include "emulator.h" #ifdef __SWITCH__ #include @@ -179,6 +183,13 @@ void linux_rpi2_init() { #endif } +#if defined(__unix__) && !defined(LIBRETRO) +static void sigintHandler(int) +{ + dc_exit(); +} +#endif + void common_linux_setup() { linux_fix_personality(); @@ -186,9 +197,30 @@ void common_linux_setup() enable_runfast(); os_InstallFaultHandler(); - signal(SIGINT, exit); +#if defined(__unix__) && !defined(LIBRETRO) + // exit cleanly on ^C + signal(SIGINT, sigintHandler); +#endif DEBUG_LOG(BOOT, "Linux paging: %ld %08X %08X", sysconf(_SC_PAGESIZE), PAGE_SIZE, PAGE_MASK); verify(PAGE_MASK==(sysconf(_SC_PAGESIZE)-1)); } + +#ifndef __APPLE__ + +void os_SetThreadName(const char *name) +{ +#ifdef __linux__ + if (strlen(name) > 16) + { + static char tmp[17]; + strncpy(tmp, name, 16); + name = tmp; + } + pthread_setname_np(pthread_self(), name); +#endif +} + +#endif + #endif // __unix__ or __APPLE__ or __SWITCH__ diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp index 2f5f9bb4f..40777bb22 100644 --- a/core/network/ggpo.cpp +++ b/core/network/ggpo.cpp @@ -783,6 +783,7 @@ std::future startNetwork() synchronized = false; return std::async(std::launch::async, []{ { + ThreadName _("GGPO-start"); std::lock_guard lock(ggpoMutex); #ifdef SYNC_TEST startSession(0, 0); diff --git a/core/network/naomi_network.h b/core/network/naomi_network.h index b84675e5c..378c0f420 100644 --- a/core/network/naomi_network.h +++ b/core/network/naomi_network.h @@ -22,6 +22,7 @@ #include "miniupnp.h" #include "cfg/option.h" #include "emulator.h" +#include "oslib/oslib.h" #include #include @@ -44,6 +45,7 @@ public: networkStopping = false; _startNow = false; return std::async(std::launch::async, [this] { + ThreadName _("NaomiNetwork-start"); bool res = startNetwork(); emu.setNetworkState(res); return res; diff --git a/core/network/picoppp.cpp b/core/network/picoppp.cpp index bc3ef5091..bbfb34bba 100644 --- a/core/network/picoppp.cpp +++ b/core/network/picoppp.cpp @@ -51,6 +51,7 @@ extern "C" { #include "miniupnp.h" #include "cfg/option.h" #include "emulator.h" +#include "oslib/oslib.h" #include #include @@ -951,6 +952,7 @@ static void *pico_thread_func(void *) std::future upnp = std::async(std::launch::async, [ports]() { // Initialize miniupnpc and map network ports + ThreadName _("UPNP-init"); MiniUPnP upnp; if (ports != nullptr && config::EnableUPnP) { @@ -1145,7 +1147,7 @@ static void *pico_thread_func(void *) return NULL; } -static cThread pico_thread(pico_thread_func, NULL); +static cThread pico_thread(pico_thread_func, nullptr, "PicoTCP"); bool start_pico() { diff --git a/core/oslib/oslib.h b/core/oslib/oslib.h index e6ad9c2e3..4c7f97eb7 100644 --- a/core/oslib/oslib.h +++ b/core/oslib/oslib.h @@ -14,6 +14,20 @@ void os_TermInput(); void os_InstallFaultHandler(); void os_UninstallFaultHandler(); void os_RunInstance(int argc, const char *argv[]); +void os_SetThreadName(const char *name); + +// raii thread name setter +class ThreadName +{ +public: + ThreadName(const char *name) { + os_SetThreadName(name); + } + ~ThreadName() { + // default name + os_SetThreadName("flycast"); + } +}; #ifdef _MSC_VER #include diff --git a/core/rend/CustomTexture.h b/core/rend/CustomTexture.h index 9eb60e1c6..29223ee8e 100644 --- a/core/rend/CustomTexture.h +++ b/core/rend/CustomTexture.h @@ -28,7 +28,7 @@ class CustomTexture { public: - CustomTexture() : loader_thread(loader_thread_func, this) {} + CustomTexture() : loader_thread(loader_thread_func, this, "CustomTexLoader") {} ~CustomTexture() { Terminate(); } u8* LoadCustomTexture(u32 hash, int& width, int& height); void LoadCustomTextureAsync(BaseTextureCacheData *texture_data); diff --git a/core/rend/boxart/boxart.cpp b/core/rend/boxart/boxart.cpp index 973cb9d5b..b5a1ae59c 100644 --- a/core/rend/boxart/boxart.cpp +++ b/core/rend/boxart/boxart.cpp @@ -19,6 +19,7 @@ #include "boxart.h" #include "gamesdb.h" #include "../game_scanner.h" +#include "oslib/oslib.h" #include GameBoxart Boxart::getBoxart(const GameMedia& media) @@ -75,6 +76,7 @@ void Boxart::fetchBoxart() if (toFetch.empty()) return; fetching = std::async(std::launch::async, [this]() { + ThreadName _("BoxArt-scraper"); if (offlineScraper == nullptr) { offlineScraper = std::unique_ptr(new OfflineScraper()); diff --git a/core/rend/game_scanner.h b/core/rend/game_scanner.h index 8dd4b5df9..33286b812 100644 --- a/core/rend/game_scanner.h +++ b/core/rend/game_scanner.h @@ -25,6 +25,7 @@ #include "types.h" #include "stdclass.h" #include "hw/naomi/naomi_roms.h" +#include "oslib/oslib.h" #include "oslib/storage.h" #include "cfg/option.h" @@ -163,6 +164,7 @@ public: scan_thread = std::unique_ptr( new std::thread([this]() { + ThreadName _("GameScanner"); if (arcade_games.empty()) for (int gameid = 0; Games[gameid].name != nullptr; gameid++) { diff --git a/core/rend/mainui.cpp b/core/rend/mainui.cpp index b5b174be7..c2dce96df 100644 --- a/core/rend/mainui.cpp +++ b/core/rend/mainui.cpp @@ -85,6 +85,7 @@ void mainui_term() void mainui_loop() { + ThreadName _("Flycast-rend"); mainui_enabled = true; mainui_init(); RenderType currentRenderer = config::RendererType; diff --git a/core/stdclass.cpp b/core/stdclass.cpp index b46215226..8751941b3 100644 --- a/core/stdclass.cpp +++ b/core/stdclass.cpp @@ -130,7 +130,10 @@ bool make_directory(const std::string& path) void cThread::Start() { verify(!thread.joinable()); - thread = std::thread(entry, param); + thread = std::thread([this]() { + ThreadName _(name); + entry(param); + }); } void cThread::WaitToEnd() diff --git a/core/stdclass.h b/core/stdclass.h index 36fe655be..8b08f0577 100644 --- a/core/stdclass.h +++ b/core/stdclass.h @@ -28,12 +28,13 @@ private: typedef void* ThreadEntryFP(void* param); ThreadEntryFP* entry; void* param; + const char *name; public: std::thread thread; - cThread(ThreadEntryFP* function, void* param) - :entry(function), param(param) {} + cThread(ThreadEntryFP* function, void* param, const char *name) + :entry(function), param(param), name(name) {} ~cThread() { WaitToEnd(); } void Start(); void WaitToEnd(); diff --git a/core/windows/winmain.cpp b/core/windows/winmain.cpp index 9924374c0..f39b51e90 100644 --- a/core/windows/winmain.cpp +++ b/core/windows/winmain.cpp @@ -508,6 +508,24 @@ void os_RunInstance(int argc, const char *argv[]) } } +void os_SetThreadName(const char *name) +{ + nowide::wstackstring wname; + if (wname.convert(name)) + { + static HRESULT (*SetThreadDescription)(HANDLE, PCWSTR); + if (SetThreadDescription == nullptr) + { + // supported in Windows 10, version 1607 or Windows Server 2016 + HINSTANCE libh = LoadLibraryW(L"KernelBase.dll"); + if (libh != NULL) + SetThreadDescription = (HRESULT (*)(HANDLE, PCWSTR))GetProcAddress(libh, "SetThreadDescription"); + } + if (SetThreadDescription != nullptr) + SetThreadDescription(GetCurrentThread(), wname.get()); + } +} + #ifdef VIDEO_ROUTING #include "SpoutSender.h" #include "SpoutDX.h" diff --git a/shell/android-studio/flycast/src/main/jni/src/Android.cpp b/shell/android-studio/flycast/src/main/jni/src/Android.cpp index bb88ef268..e07870fcd 100644 --- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp @@ -211,7 +211,7 @@ extern "C" JNIEXPORT jstring JNICALL Java_com_reicast_emulator_emu_JNIdc_initEnv else { static std::string crashPath; - static cThread uploadThread(uploadCrashThread, &crashPath); + static cThread uploadThread(uploadCrashThread, &crashPath, "SentryUpload"); crashPath = get_writable_config_path(""); uploadThread.Start(); } @@ -337,7 +337,7 @@ static void *render_thread_func(void *) return NULL; } -static cThread render_thread(render_thread_func, NULL); +static cThread render_thread(render_thread_func, nullptr, "Flycast-rend"); extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_JNIdc_rendinitNative(JNIEnv * env, jobject obj, jobject surface, jint width, jint height) { diff --git a/shell/apple/common/util.mm b/shell/apple/common/util.mm new file mode 100644 index 000000000..2e282aa60 --- /dev/null +++ b/shell/apple/common/util.mm @@ -0,0 +1,27 @@ +/* + Copyright 2023 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#import +#include "oslib/oslib.h" + +void os_SetThreadName(const char *name) +{ + NSString *nsname = [NSString stringWithCString:name + encoding:[NSString defaultCStringEncoding]]; + [[NSThread currentThread] setName:nsname]; +} diff --git a/shell/switch/ucontext.h b/shell/switch/ucontext.h index c2b2312c0..c14f1ae09 100644 --- a/shell/switch/ucontext.h +++ b/shell/switch/ucontext.h @@ -1,5 +1,5 @@ #pragma once -#include +#include "nswitch.h" typedef struct { From 9e916ca1c9228cde8bce0d537ace900874e12da6 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 10 Apr 2024 17:24:47 +0200 Subject: [PATCH 40/86] minor input and lua fixes Make macOS keyboard class with haptic code Call SDL_JoystickSetPlayerIndex using maple port Add Network event for LUA Set proper controller unique id on iOS rumblePower wasn't copied over new mappings. --- core/input/mapping.h | 1 + core/lua/lua.cpp | 5 + core/sdl/sdl.cpp | 5 + core/sdl/sdl_gamepad.h | 7 ++ core/sdl/sdl_keyboard.h | 85 -------------- core/sdl/sdl_keyboard_mac.h | 111 ++++++++++++++++++ .../apple/emulator-ios/emulator/ios_gamepad.h | 9 +- 7 files changed, 134 insertions(+), 89 deletions(-) create mode 100644 core/sdl/sdl_keyboard_mac.h diff --git a/core/input/mapping.h b/core/input/mapping.h index 4e8925005..8adac51af 100644 --- a/core/input/mapping.h +++ b/core/input/mapping.h @@ -36,6 +36,7 @@ public: name = other.name; dead_zone = other.dead_zone; saturation = other.saturation; + rumblePower = other.rumblePower; for (int port = 0; port < 4; port++) { buttons[port] = other.buttons[port]; diff --git a/core/lua/lua.cpp b/core/lua/lua.cpp index da69edb79..303543d0a 100644 --- a/core/lua/lua.cpp +++ b/core/lua/lua.cpp @@ -71,6 +71,9 @@ static void emuEventCallback(Event event, void *) case Event::VBlank: key = "vblank"; break; + case Event::Network: + key = "network"; + break; } if (v[key].isFunction()) v[key](); @@ -619,6 +622,7 @@ void init() EventManager::listen(Event::Terminate, emuEventCallback); EventManager::listen(Event::LoadState, emuEventCallback); EventManager::listen(Event::VBlank, emuEventCallback); + EventManager::listen(Event::Network, emuEventCallback); doExec(initFile); } @@ -633,6 +637,7 @@ void term() EventManager::unlisten(Event::Terminate, emuEventCallback); EventManager::unlisten(Event::LoadState, emuEventCallback); EventManager::unlisten(Event::VBlank, emuEventCallback); + EventManager::unlisten(Event::Network, emuEventCallback); lua_close(L); L = nullptr; } diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index 6cb4c8dc6..b3846b048 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -14,6 +14,7 @@ #include "hw/maple/maple_devs.h" #include "sdl_gamepad.h" #include "sdl_keyboard.h" +#include "sdl_keyboard_mac.h" #include "wsi/context.h" #include "emulator.h" #include "stdclass.h" @@ -171,7 +172,11 @@ static void checkRawInput() #else if (!sdl_keyboard) { +#ifdef __APPLE__ + sdl_keyboard = std::make_shared(0); +#else sdl_keyboard = std::make_shared(0); +#endif GamepadDevice::Register(sdl_keyboard); } #endif diff --git a/core/sdl/sdl_gamepad.h b/core/sdl/sdl_gamepad.h index 6bb9e7f99..3c1a4cd20 100644 --- a/core/sdl/sdl_gamepad.h +++ b/core/sdl/sdl_gamepad.h @@ -208,6 +208,7 @@ public: #endif hasAnalogStick = SDL_JoystickNumAxes(sdl_joystick) > 0; + set_maple_port(maple_port); } bool gamepad_axis_input(u32 code, int value) override @@ -217,6 +218,12 @@ public: return GamepadDevice::gamepad_axis_input(code, value); } + void set_maple_port(int port) override + { + GamepadDevice::set_maple_port(port); + SDL_JoystickSetPlayerIndex(sdl_joystick, port <= 3 ? port : -1); + } + u16 getRumbleIntensity(float power) { return (u16)std::min(power * 65535.f / std::pow(1.06f, 100.f - rumblePower), 65535.f); } diff --git a/core/sdl/sdl_keyboard.h b/core/sdl/sdl_keyboard.h index b524ef563..8455acd6b 100644 --- a/core/sdl/sdl_keyboard.h +++ b/core/sdl/sdl_keyboard.h @@ -2,25 +2,6 @@ #include "input/keyboard_device.h" #include "sdl.h" -#ifdef __APPLE__ -#include -#include -#include -// Rumbling Taptic Engine by Private MultitouchSupport.framework -extern "C" { -typedef void *MTDeviceRef; -bool MTDeviceIsAvailable(void); -MTDeviceRef MTDeviceCreateDefault(void); -OSStatus MTDeviceGetDeviceID(MTDeviceRef, uint64_t*) __attribute__ ((weak_import)); -CFTypeRef MTActuatorCreateFromDeviceID(UInt64 deviceID); -IOReturn MTActuatorOpen(CFTypeRef actuatorRef); -IOReturn MTActuatorClose(CFTypeRef actuatorRef); -IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 unknown1, Float32 unknown2, Float32 unknown3); -bool MTActuatorIsOpen(CFTypeRef actuatorRef); -enum ActuatePattern { minimal = 3, weak = 5, medium = 4, strong = 6 }; -} -#endif - class SDLKeyboardDevice : public KeyboardDevice { public: @@ -54,12 +35,6 @@ public: } else input_mapper = getDefaultMapping(); - -#ifdef __APPLE__ - uint64_t deviceID; - if ( MTDeviceIsAvailable() && MTDeviceGetDeviceID(MTDeviceCreateDefault(), &deviceID) == 0 && (vib_device = MTActuatorCreateFromDeviceID(deviceID)) != NULL && MTActuatorOpen(vib_device) == kIOReturnSuccess) - rumbleEnabled = true; -#endif } const char *get_button_name(u32 code) override @@ -70,59 +45,6 @@ public: return name; } -#ifdef __APPLE__ - void rumble(float power, float inclination, u32 duration_ms) override - { - if (rumbleEnabled) - { - vib_stop_time = os_GetSeconds() + duration_ms / 1000.0; - - __block int pattern; - if (power >= 0.75) - pattern = ActuatePattern::strong; - else if (power >= 0.5) - pattern = ActuatePattern::medium; - else if (power >= 0.25) - pattern = ActuatePattern::weak; - else if (power > 0) - pattern = ActuatePattern::minimal; - else - { - while(!vib_timer_stack.empty()) - { - dispatch_source_cancel(vib_timer_stack.top()); - vib_timer_stack.pop(); - } - return; - } - // Since the Actuator API does not support duration - // using a interval timer with `10ms * rumblePower percentage` to fake it - __block dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); - vib_timer_stack.push(_timer); - dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 10 * NSEC_PER_MSEC * rumblePower / 100.f, 0); - - dispatch_source_set_event_handler(_timer, ^{ - if ( vib_stop_time - os_GetSeconds() < 0 ) - { - dispatch_source_cancel(_timer); - return; - } - MTActuatorActuate(vib_device, pattern, 0, 0.0, 0.0); - }); - - dispatch_resume(_timer); - } - } - - ~SDLKeyboardDevice() { - if (rumbleEnabled) - { - MTActuatorClose(vib_device); - CFRelease(vib_device); - } - } -#endif - void input(SDL_Scancode scancode, bool pressed) { u8 keycode; @@ -132,11 +54,4 @@ public: keycode = (u8)scancode; KeyboardDevice::input(keycode, pressed, 0); } - -#ifdef __APPLE__ -private: - std::stack vib_timer_stack; - CFTypeRef vib_device = NULL; - double vib_stop_time = 0; -#endif }; diff --git a/core/sdl/sdl_keyboard_mac.h b/core/sdl/sdl_keyboard_mac.h new file mode 100644 index 000000000..abb53ac97 --- /dev/null +++ b/core/sdl/sdl_keyboard_mac.h @@ -0,0 +1,111 @@ +/* + Copyright 2022 edw + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#pragma once +#ifdef __APPLE__ +#include "sdl_keyboard.h" +#include +#include +#include + +// Rumbling Taptic Engine by Private MultitouchSupport.framework +extern "C" { +typedef void *MTDeviceRef; +bool MTDeviceIsAvailable(void); +MTDeviceRef MTDeviceCreateDefault(void); +OSStatus MTDeviceGetDeviceID(MTDeviceRef, uint64_t*) __attribute__ ((weak_import)); +CFTypeRef MTActuatorCreateFromDeviceID(UInt64 deviceID); +IOReturn MTActuatorOpen(CFTypeRef actuatorRef); +IOReturn MTActuatorClose(CFTypeRef actuatorRef); +IOReturn MTActuatorActuate(CFTypeRef actuatorRef, SInt32 actuationID, UInt32 unknown1, Float32 unknown2, Float32 unknown3); +bool MTActuatorIsOpen(CFTypeRef actuatorRef); +enum ActuatePattern { minimal = 3, weak = 5, medium = 4, strong = 6 }; +} + +class SDLMacKeyboard : public SDLKeyboardDevice +{ +public: + SDLMacKeyboard(int maple_port) : SDLKeyboardDevice(maple_port) + { + uint64_t deviceID; + if (MTDeviceIsAvailable() + && MTDeviceGetDeviceID(MTDeviceCreateDefault(), &deviceID) == 0 + && (vib_device = MTActuatorCreateFromDeviceID(deviceID)) != NULL + && MTActuatorOpen(vib_device) == kIOReturnSuccess) + rumbleEnabled = true; + } + + void rumble(float power, float inclination, u32 duration_ms) override + { + if (!rumbleEnabled) + return; + + vib_stop_time = os_GetSeconds() + duration_ms / 1000.0; + + __block int pattern; + if (power >= 0.75) + pattern = ActuatePattern::strong; + else if (power >= 0.5) + pattern = ActuatePattern::medium; + else if (power >= 0.25) + pattern = ActuatePattern::weak; + else if (power > 0) + pattern = ActuatePattern::minimal; + else + { + while(!vib_timer_stack.empty()) + { + dispatch_source_cancel(vib_timer_stack.top()); + vib_timer_stack.pop(); + } + return; + } + // Since the Actuator API does not support duration + // using a interval timer with `10ms * rumblePower percentage` to fake it + __block dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); + vib_timer_stack.push(_timer); + dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 10 * NSEC_PER_MSEC * rumblePower / 100.f, 0); + + dispatch_source_set_event_handler(_timer, ^{ + if ( vib_stop_time - os_GetSeconds() < 0 ) + { + dispatch_source_cancel(_timer); + return; + } + MTActuatorActuate(vib_device, pattern, 0, 0.0, 0.0); + }); + + dispatch_resume(_timer); + } + + ~SDLMacKeyboard() + { + if (rumbleEnabled) + { + MTActuatorClose(vib_device); + CFRelease(vib_device); + } + } + +private: + std::stack vib_timer_stack; + CFTypeRef vib_device = NULL; + double vib_stop_time = 0; +}; + +#endif // _APPLE_ diff --git a/shell/apple/emulator-ios/emulator/ios_gamepad.h b/shell/apple/emulator-ios/emulator/ios_gamepad.h index 5b4b33317..08159cc84 100644 --- a/shell/apple/emulator-ios/emulator/ios_gamepad.h +++ b/shell/apple/emulator-ios/emulator/ios_gamepad.h @@ -139,14 +139,14 @@ public: class IOSGamepad : public GamepadDevice { public: - IOSGamepad(int port, GCController *controller) : GamepadDevice(port, "iOS"), gcController(controller) + IOSGamepad(int port, GCController *controller, int index) : GamepadDevice(port, "iOS"), gcController(controller) { set_maple_port(port); if (gcController.vendorName != nullptr) _name = [gcController.vendorName UTF8String]; else _name = "MFi Gamepad"; - //_unique_id = ? + _unique_id = "ios-" + std::to_string(index); INFO_LOG(INPUT, "iOS: Opened joystick %d: '%s'", port, _name.c_str()); loadMapping(); @@ -459,8 +459,9 @@ public: return; if (controller.extendedGamepad == nullptr && controller.gamepad == nullptr) return; - int port = std::min((int)controllers.size(), 3); - controllers[controller] = std::make_shared(port, controller); + int index = (int)controllers.size(); + int port = std::min(index, 3); + controllers[controller] = std::make_shared(port, controller, index); GamepadDevice::Register(controllers[controller]); } From 108c8a4b5d5ab7c78fa4d57292a25c16cea120dc Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 10 Apr 2024 17:31:23 +0200 Subject: [PATCH 41/86] vk: move macOS video routing code from context to renderer --- core/rend/vulkan/vulkan_context.cpp | 4 ---- core/rend/vulkan/vulkan_renderer.h | 9 +++++++++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index 48d4b61eb..b7c19072b 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -1030,10 +1030,6 @@ void VulkanContext::term() renderCompleteSemaphores.clear(); drawFences.clear(); allocator.Term(); -#if defined(VIDEO_ROUTING) && defined(TARGET_MAC) - extern void os_VideoRoutingTermVk(); - os_VideoRoutingTermVk(); -#endif #ifndef USE_SDL surface.reset(); #else diff --git a/core/rend/vulkan/vulkan_renderer.h b/core/rend/vulkan/vulkan_renderer.h index fc144ec8e..91a319082 100644 --- a/core/rend/vulkan/vulkan_renderer.h +++ b/core/rend/vulkan/vulkan_renderer.h @@ -31,6 +31,8 @@ #include #include +void os_VideoRoutingTermVk(); + class BaseVulkanRenderer : public Renderer { protected: @@ -76,6 +78,9 @@ public: { GetContext()->WaitIdle(); GetContext()->PresentFrame(nullptr, nullptr, vk::Extent2D(), 0); +#if defined(VIDEO_ROUTING) && defined(TARGET_MAC) + os_VideoRoutingTermVk(); +#endif framebufferDrawer.reset(); quadPipeline.reset(); osdBuffer.reset(); @@ -249,6 +254,10 @@ public: extern void os_VideoRoutingPublishFrameTexture(const vk::Device& device, const vk::Image& image, const vk::Queue& queue, float x, float y, float w, float h); os_VideoRoutingPublishFrameTexture(device, srcImage, graphicsQueue, 0, 0, targetWidth, targetHeight); } + else + { + os_VideoRoutingTermVk(); + } #endif } From 5541d34a9e3a456246bbdfa5dabb39900bb9c581 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 10 Apr 2024 18:10:21 +0200 Subject: [PATCH 42/86] lr win32 build fix --- shell/libretro/oslib.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/shell/libretro/oslib.cpp b/shell/libretro/oslib.cpp index 4f7bfa872..cbb46b3bc 100644 --- a/shell/libretro/oslib.cpp +++ b/shell/libretro/oslib.cpp @@ -139,3 +139,8 @@ void dc_loadstate(int index = 0) { die("unsupported"); } + +#ifdef _WIN32 +void os_SetThreadName(const char *name) { +} +#endif From 06a6e26588629e014c5842ad372870796023bf46 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Thu, 11 Apr 2024 15:25:25 +0200 Subject: [PATCH 43/86] get rid of os_GetSeconds() replace it with std::chrono-based getTimeMs() --- core/hw/modem/modem.cpp | 11 ++-- core/hw/pvr/spg.cpp | 43 +++++++------- core/hw/sh4/modules/fastmmu.cpp | 7 ++- core/input/gamepad_device.cpp | 12 ++-- core/input/gamepad_device.h | 2 +- core/linux-dist/evdev_gamepad.h | 8 +-- core/linux/common.cpp | 56 ++++++------------- core/log/LogManager.cpp | 10 ++-- core/network/picoppp.cpp | 5 +- core/oslib/oslib.h | 1 - core/rend/boxart/gamesdb.cpp | 11 ++-- core/rend/boxart/gamesdb.h | 2 +- core/rend/gui.cpp | 24 ++++---- core/sdl/sdl.cpp | 6 +- core/sdl/sdl_gamepad.h | 8 +-- core/sdl/sdl_keyboard_mac.h | 7 ++- core/stdclass.cpp | 10 ++++ core/stdclass.h | 2 + core/types.h | 3 - core/windows/fault_handler.cpp | 15 ----- .../src/main/jni/src/android_keyboard.h | 6 +- .../emulator/FlycastViewController.mm | 6 +- shell/libretro/libretro.cpp | 11 +--- shell/switch/switch_gamepad.h | 8 +-- tests/src/test_stubs.cpp | 17 ------ 25 files changed, 119 insertions(+), 172 deletions(-) diff --git a/core/hw/modem/modem.cpp b/core/hw/modem/modem.cpp index 93fda73d8..f3d6e904d 100644 --- a/core/hw/modem/modem.cpp +++ b/core/hw/modem/modem.cpp @@ -28,10 +28,7 @@ #include "network/picoppp.h" #include "serialize.h" #include "cfg/option.h" - -#ifndef NDEBUG -#include "oslib/oslib.h" -#endif +#include "stdclass.h" #include #define MODEM_COUNTRY_RES 0 @@ -127,7 +124,7 @@ static u64 last_dial_time; static bool data_sent; #ifndef NDEBUG -static double last_comm_stats; +static u64 last_comm_stats; static int sent_bytes; static int recvd_bytes; static FILE *recv_fp; @@ -137,7 +134,7 @@ static FILE *sent_fp; static int modem_sched_func(int tag, int cycles, int jitter, void *arg) { #ifndef NDEBUG - if (os_GetSeconds() - last_comm_stats >= 2) + if (getTimeMs() - last_comm_stats >= 2000) { if (last_comm_stats != 0) { @@ -147,7 +144,7 @@ static int modem_sched_func(int tag, int cycles, int jitter, void *arg) sent_bytes = 0; recvd_bytes = 0; } - last_comm_stats = os_GetSeconds(); + last_comm_stats = getTimeMs(); } #endif int callback_cycles = 0; diff --git a/core/hw/pvr/spg.cpp b/core/hw/pvr/spg.cpp index ff3b6f320..6cdc5bec0 100755 --- a/core/hw/pvr/spg.cpp +++ b/core/hw/pvr/spg.cpp @@ -1,13 +1,13 @@ -#include #include "spg.h" #include "hw/holly/holly_intc.h" #include "hw/holly/sb.h" #include "hw/sh4/sh4_sched.h" -#include "oslib/oslib.h" #include "hw/maple/maple_if.h" #include "serialize.h" #include "network/ggpo.h" #include "hw/pvr/Renderer_if.h" +#include "stdclass.h" +#include #ifdef TEST_AUTOMATION #include "input/gamepad_device.h" @@ -21,7 +21,7 @@ static u32 prv_cur_scanline = -1; #if !defined(NDEBUG) || defined(DEBUGFAST) static u32 vblk_cnt; -static float last_fps; +static u64 last_fps; #endif // 27 mhz pixel clock @@ -156,17 +156,18 @@ static int spg_line_sched(int tag, int cycles, int jitter, void *arg) rend_vblank(); - double now = os_GetSeconds() * 1000000.0; + u64 now = getTimeMs(); cpu_time_idx = (cpu_time_idx + 1) % cpu_cycles.size(); if (cpu_cycles[cpu_time_idx] != 0) { u32 cycle_span = (u32)(sh4_sched_now64() - cpu_cycles[cpu_time_idx]); - double time_span = now - real_times[cpu_time_idx]; - double cpu_speed = ((double)cycle_span / time_span) / (SH4_MAIN_CLOCK / 100000000); - SH4FastEnough = cpu_speed >= 85.0; + u64 time_span = now - real_times[cpu_time_idx]; + float cpu_speed = ((float)cycle_span / time_span) / (SH4_MAIN_CLOCK / 100000); + SH4FastEnough = cpu_speed >= 85.f; } - else + else { SH4FastEnough = false; + } cpu_cycles[cpu_time_idx] = sh4_sched_now64(); real_times[cpu_time_idx] = now; @@ -176,15 +177,15 @@ static int spg_line_sched(int tag, int cycles, int jitter, void *arg) #if !defined(NDEBUG) || defined(DEBUGFAST) vblk_cnt++; - if ((os_GetSeconds()-last_fps)>2) + if (getTimeMs() - last_fps >= 2000) { static int Last_FC; - double ts=os_GetSeconds()-last_fps; - double spd_fps=(FrameCount-Last_FC)/ts; - double spd_vbs=vblk_cnt/ts; - double spd_cpu=spd_vbs*Frame_Cycles; - spd_cpu/=1000000; //mrhz kthx - double fullvbs=(spd_vbs/spd_cpu)*200; + double ts = ((double)getTimeMs() - last_fps) / 1000.0; + double spd_fps = (FrameCount - Last_FC) / ts; + double spd_vbs = vblk_cnt / ts; + double spd_cpu = spd_vbs * Frame_Cycles; + spd_cpu /= 1000000.0; //mrhz kthx + double fullvbs = (spd_vbs / spd_cpu) * 200.0; Last_FC=FrameCount; @@ -210,13 +211,13 @@ static int spg_line_sched(int tag, int cycles, int jitter, void *arg) double full_rps = spd_fps + fskip / ts; - INFO_LOG(COMMON, "%s/%c - %4.2f - %4.2f - V: %4.2f (%.2f, %s%s%4.2f) R: %4.2f+%4.2f", - VER_SHORTNAME,'n',mspdf,spd_cpu*100/200,spd_vbs, - spd_vbs/full_rps,mode,res,fullvbs, - spd_fps,fskip/ts); + INFO_LOG(COMMON, "SPG - %4.2f - %4.2f - V: %4.2f (%.2f, %s%s%4.2f) R: %4.2f+%4.2f", + mspdf, spd_cpu * 100 / 200, spd_vbs, + spd_vbs / full_rps, mode, res, fullvbs, + spd_fps, fskip / ts); - fskip=0; - last_fps=os_GetSeconds(); + fskip = 0; + last_fps = getTimeMs(); } #endif } diff --git a/core/hw/sh4/modules/fastmmu.cpp b/core/hw/sh4/modules/fastmmu.cpp index e599d03b5..bc9c08487 100644 --- a/core/hw/sh4/modules/fastmmu.cpp +++ b/core/hw/sh4/modules/fastmmu.cpp @@ -20,6 +20,7 @@ #include "hw/sh4/sh4_if.h" #include "hw/sh4/sh4_core.h" #include "types.h" +#include "stdclass.h" #ifdef FAST_MMU @@ -156,7 +157,7 @@ int main(int argc, char *argv[]) addrs.push_back(random()); asids.push_back(666); } - double start = os_GetSeconds(); + u64 start = getTimeMs(); int success = 0; const int loops = 100000; for (int i = 0; i < loops; i++) @@ -170,8 +171,8 @@ int main(int argc, char *argv[]) success++; } } - double end = os_GetSeconds(); - printf("Lookup time: %f ms. Success rate %f max_len %d\n", (end - start) * 1000.0 / addrs.size(), (double)success / addrs.size() / loops, 0/*max_length*/); + u64 end = getTimeMs(); + printf("Lookup time: %f ms. Success rate %f max_len %d\n", ((double)end - start) / addrs.size(), (double)success / addrs.size() / loops, 0/*max_length*/); } #endif diff --git a/core/input/gamepad_device.cpp b/core/input/gamepad_device.cpp index 8fdbc1b7e..702fcbc65 100644 --- a/core/input/gamepad_device.cpp +++ b/core/input/gamepad_device.cpp @@ -19,7 +19,7 @@ #include "gamepad_device.h" #include "cfg/cfg.h" -#include "oslib/oslib.h" +#include "stdclass.h" #include "rend/gui.h" #include "emulator.h" #include "hw/maple/maple_devs.h" @@ -152,7 +152,7 @@ bool GamepadDevice::handleButtonInput(int port, DreamcastKey key, bool pressed) bool GamepadDevice::gamepad_btn_input(u32 code, bool pressed) { if (_input_detected != nullptr && _detecting_button - && os_GetSeconds() >= _detection_start_time && pressed) + && getTimeMs() >= _detection_start_time && pressed) { _input_detected(code, false, false); _input_detected = nullptr; @@ -207,7 +207,7 @@ bool GamepadDevice::gamepad_axis_input(u32 code, int value) { bool positive = value >= 0; if (_input_detected != NULL && _detecting_axis - && os_GetSeconds() >= _detection_start_time && std::abs(value) >= 16384) + && getTimeMs() >= _detection_start_time && std::abs(value) >= 16384) { _input_detected(code, true, positive); _input_detected = nullptr; @@ -505,7 +505,7 @@ void GamepadDevice::detect_btn_input(input_detected_cb button_pressed) _input_detected = button_pressed; _detecting_button = true; _detecting_axis = false; - _detection_start_time = os_GetSeconds() + 0.2; + _detection_start_time = getTimeMs() + 200; } void GamepadDevice::detect_axis_input(input_detected_cb axis_moved) @@ -513,7 +513,7 @@ void GamepadDevice::detect_axis_input(input_detected_cb axis_moved) _input_detected = axis_moved; _detecting_button = false; _detecting_axis = true; - _detection_start_time = os_GetSeconds() + 0.2; + _detection_start_time = getTimeMs() + 200; } void GamepadDevice::detectButtonOrAxisInput(input_detected_cb input_changed) @@ -521,7 +521,7 @@ void GamepadDevice::detectButtonOrAxisInput(input_detected_cb input_changed) _input_detected = input_changed; _detecting_button = true; _detecting_axis = true; - _detection_start_time = os_GetSeconds() + 0.2; + _detection_start_time = getTimeMs() + 200; } #ifdef TEST_AUTOMATION diff --git a/core/input/gamepad_device.h b/core/input/gamepad_device.h index 4941f1c7f..aef633870 100644 --- a/core/input/gamepad_device.h +++ b/core/input/gamepad_device.h @@ -177,7 +177,7 @@ private: int _maple_port; bool _detecting_button = false; bool _detecting_axis = false; - double _detection_start_time = 0.0; + u64 _detection_start_time = 0; input_detected_cb _input_detected; bool _remappable; u32 digitalToAnalogState[4]; diff --git a/core/linux-dist/evdev_gamepad.h b/core/linux-dist/evdev_gamepad.h index 263868453..aae93e552 100644 --- a/core/linux-dist/evdev_gamepad.h +++ b/core/linux-dist/evdev_gamepad.h @@ -1,7 +1,7 @@ #pragma once #include "evdev.h" #include "input/gamepad_device.h" -#include "oslib/oslib.h" +#include "stdclass.h" #include #include @@ -81,7 +81,7 @@ public: void rumble(float power, float inclination, u32 duration_ms) override { vib_inclination = inclination * power; - vib_stop_time = os_GetSeconds() + duration_ms / 1000.0; + vib_stop_time = getTimeMs() + duration_ms; do_rumble(power, duration_ms); } @@ -89,7 +89,7 @@ public: { if (vib_inclination > 0) { - int rem_time = (vib_stop_time - os_GetSeconds()) * 1000; + int rem_time = vib_stop_time - getTimeMs(); if (rem_time <= 0) vib_inclination = 0; else @@ -308,7 +308,7 @@ private: std::string _devnode; int _rumble_effect_id = -1; float vib_inclination = 0; - double vib_stop_time = 0; + u64 vib_stop_time = 0; std::map axis_min_values; std::map axis_ranges; static std::map> evdev_gamepads; diff --git a/core/linux/common.cpp b/core/linux/common.cpp index 37c5151ca..00453b762 100644 --- a/core/linux/common.cpp +++ b/core/linux/common.cpp @@ -11,9 +11,6 @@ #if defined(__linux__) && !defined(__ANDROID__) #include #endif -#if !defined(TARGET_BSD) && !defined(__ANDROID__) && defined(TARGET_VIDEOCORE) - #include -#endif #include #ifdef __linux__ #include @@ -123,14 +120,6 @@ void os_UninstallFaultHandler() } #endif // !defined(TARGET_NO_EXCEPTIONS) -double os_GetSeconds() -{ - timeval a; - gettimeofday (&a,0); - static u64 tvs_base=a.tv_sec; - return a.tv_sec-tvs_base+a.tv_usec/1000000.0; -} - #if !defined(__unix__) && !defined(LIBRETRO) && !defined(__SWITCH__) [[noreturn]] void os_DebugBreak() { @@ -138,11 +127,15 @@ double os_GetSeconds() } #endif -void enable_runfast() +// RunFast mode is the combination of the following conditions: +// * the VFP11 coprocessor is in flush-to-zero mode +// * the VFP11 coprocessor is in default NaN mode +// * all exception enable bits are cleared. +static void enable_runfast() { - #if HOST_CPU==CPU_ARM && !defined(ARMCC) - static const unsigned int x = 0x04086060; - static const unsigned int y = 0x03000000; +#if HOST_CPU == CPU_ARM && !defined(ARMCC) + static const unsigned int x = 0x04086060; // reset and disable FP exceptions, flush-to-zero, default NaN mode + static const unsigned int y = 0x03000000; // round to zero int r; asm volatile ( "fmrx %0, fpscr \n\t" //r0 = FPSCR @@ -154,32 +147,18 @@ void enable_runfast() ); DEBUG_LOG(BOOT, "ARM VFP-Run Fast (NFP) enabled !"); - #endif -} - -void linux_fix_personality() { -#if defined(__linux__) && !defined(__ANDROID__) - DEBUG_LOG(BOOT, "Personality: %08X", personality(0xFFFFFFFF)); - personality(~READ_IMPLIES_EXEC & personality(0xFFFFFFFF)); - DEBUG_LOG(BOOT, "Updated personality: %08X", personality(0xFFFFFFFF)); #endif } -void linux_rpi2_init() { -#if !defined(TARGET_BSD) && !defined(__ANDROID__) && defined(TARGET_VIDEOCORE) - void* handle; - void (*rpi_bcm_init)(void); - - handle = dlopen("libbcm_host.so", RTLD_LAZY); - - if (handle) { - DEBUG_LOG(BOOT, "found libbcm_host"); - *(void**) (&rpi_bcm_init) = dlsym(handle, "bcm_host_init"); - if (rpi_bcm_init) { - DEBUG_LOG(BOOT, "rpi2: bcm_init"); - rpi_bcm_init(); - } - } +// Some old CPUs lack the NX (no exec) flag so READ_IMPLIES_EXEC is set by default on these platforms. +// However resetting the flag isn't going to magically change the way the CPU works. So I wonder how useful this is. +// It's not needed on modern 64-bit architectures anyway. +static void linux_fix_personality() +{ +#if defined(__linux__) && !defined(__ANDROID__) && (HOST_CPU == CPU_X86 || HOST_CPU == CPU_ARM) + DEBUG_LOG(BOOT, "Personality: %08X", personality(0xFFFFFFFF)); + personality(~READ_IMPLIES_EXEC & personality(0xFFFFFFFF)); + DEBUG_LOG(BOOT, "Updated personality: %08X", personality(0xFFFFFFFF)); #endif } @@ -193,7 +172,6 @@ static void sigintHandler(int) void common_linux_setup() { linux_fix_personality(); - linux_rpi2_init(); enable_runfast(); os_InstallFaultHandler(); diff --git a/core/log/LogManager.cpp b/core/log/LogManager.cpp index d86102ac4..dacaac706 100644 --- a/core/log/LogManager.cpp +++ b/core/log/LogManager.cpp @@ -175,10 +175,12 @@ LogManager::~LogManager() // in the form 00:00:000. static std::string GetTimeFormatted() { - double now = os_GetSeconds(); - u32 minutes = (u32)now / 60; - u32 seconds = (u32)now % 60; - u32 ms = (now - (u32)now) * 1000; + u64 now = getTimeMs(); + u32 ms = (u32)(now % 1000); + now /= 1000; + u32 seconds = (u32)(now % 60); + now /= 60; + u32 minutes = (u32)now; return StringFromFormat("%02d:%02d:%03d", minutes, seconds, ms); } diff --git a/core/network/picoppp.cpp b/core/network/picoppp.cpp index bbfb34bba..7ba626a94 100644 --- a/core/network/picoppp.cpp +++ b/core/network/picoppp.cpp @@ -26,9 +26,6 @@ #include "stdclass.h" //#define BBA_PCAPNG_DUMP -#ifdef BBA_PCAPNG_DUMP -#include "oslib/oslib.h" -#endif #ifdef __MINGW32__ #define _POSIX_SOURCE @@ -883,7 +880,7 @@ static void dumpFrame(const u8 *frame, u32 size) fwrite(&roundedSize, sizeof(roundedSize), 1, pcapngDump); u32 ifId = 0; fwrite(&ifId, sizeof(ifId), 1, pcapngDump); - u64 now = (u64)(os_GetSeconds() * 1000000.0); + u64 now = getTimeMs() * 1000; fwrite((u32 *)&now + 1, 4, 1, pcapngDump); fwrite(&now, 4, 1, pcapngDump); fwrite(&size, sizeof(size), 1, pcapngDump); diff --git a/core/oslib/oslib.h b/core/oslib/oslib.h index 4c7f97eb7..91aff2106 100644 --- a/core/oslib/oslib.h +++ b/core/oslib/oslib.h @@ -5,7 +5,6 @@ #endif void os_SetWindowText(const char* text); -double os_GetSeconds(); void os_DoEvents(); void os_CreateWindow(); diff --git a/core/rend/boxart/gamesdb.cpp b/core/rend/boxart/gamesdb.cpp index 063c3587e..f03fc03c6 100644 --- a/core/rend/boxart/gamesdb.cpp +++ b/core/rend/boxart/gamesdb.cpp @@ -19,7 +19,6 @@ #include "gamesdb.h" #include "http_client.h" #include "stdclass.h" -#include "oslib/oslib.h" #include "emulator.h" #define APIKEY "3fcc5e726a129924972be97abfd577ac5311f8f12398a9d9bcb5a377d4656fa8" @@ -74,9 +73,9 @@ void TheGamesDb::copyFile(const std::string& from, const std::string& to) json TheGamesDb::httpGet(const std::string& url) { - if (os_GetSeconds() < blackoutPeriod) + if (getTimeMs() < blackoutPeriod) throw std::runtime_error(""); - blackoutPeriod = 0.0; + blackoutPeriod = 0; DEBUG_LOG(COMMON, "TheGameDb: GET %s", url.c_str()); std::vector receivedData; @@ -84,9 +83,9 @@ json TheGamesDb::httpGet(const std::string& url) bool success = http::success(status); if (status == 403) // hit rate-limit cap - blackoutPeriod = os_GetSeconds() + 60.0; + blackoutPeriod = getTimeMs() + 60 * 1000; else if (!success) - blackoutPeriod = os_GetSeconds() + 1.0; + blackoutPeriod = getTimeMs() + 1000; if (!success || receivedData.empty()) throw std::runtime_error("http error"); @@ -382,7 +381,7 @@ void TheGamesDb::fetchByUids(std::vector& items) void TheGamesDb::scrape(std::vector& items) { - if (os_GetSeconds() < blackoutPeriod) + if (getTimeMs() < blackoutPeriod) throw std::runtime_error(""); blackoutPeriod = 0.0; diff --git a/core/rend/boxart/gamesdb.h b/core/rend/boxart/gamesdb.h index 90e179074..4d8445971 100644 --- a/core/rend/boxart/gamesdb.h +++ b/core/rend/boxart/gamesdb.h @@ -47,7 +47,7 @@ private: int dreamcastPlatformId = 0; int arcadePlatformId = 0; - double blackoutPeriod = 0.0; + u64 blackoutPeriod = 0; std::map boxartCache; // key: url, value: local file path }; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index ea07d6cf2..db6398486 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -78,7 +78,7 @@ static float mouseWheel; static std::string error_msg; static bool error_msg_shown; static std::string osd_message; -static double osd_message_end; +static u64 osd_message_end; static std::mutex osd_message_mutex; static void (*showOnScreenKeyboard)(bool show); static bool keysUpNextFrame[512]; @@ -920,7 +920,7 @@ static std::shared_ptr mapped_device; static u32 mapped_code; static bool analogAxis; static bool positiveDirection; -static double map_start_time; +static u64 map_start_time; static bool arcade_button_mode; static u32 gamepad_port; @@ -986,8 +986,8 @@ static void detect_input_popup(const Mapping *mapping) if (ImGui::BeginPopupModal("Map Control", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { ImGui::Text("Waiting for control '%s'...", mapping->name); - double now = os_GetSeconds(); - ImGui::Text("Time out in %d s", (int)(5 - (now - map_start_time))); + u64 now = getTimeMs(); + ImGui::Text("Time out in %d s", (int)(5 - (now - map_start_time) / 1000)); if (mapped_code != (u32)-1) { std::shared_ptr input_mapping = mapped_device->get_input_mapping(); @@ -1012,7 +1012,7 @@ static void detect_input_popup(const Mapping *mapping) mapped_device = NULL; ImGui::CloseCurrentPopup(); } - else if (now - map_start_time >= 5) + else if (now - map_start_time >= 5000) { mapped_device = NULL; ImGui::CloseCurrentPopup(); @@ -1239,7 +1239,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep ImGui::NextColumn(); if (ImGui::Button("Map")) { - map_start_time = os_GetSeconds(); + map_start_time = getTimeMs(); ImGui::OpenPopup("Map Control"); mapped_device = gamepad; mapped_code = -1; @@ -2859,13 +2859,13 @@ void gui_display_notification(const char *msg, int duration) { std::lock_guard lock(osd_message_mutex); osd_message = msg; - osd_message_end = os_GetSeconds() + (double)duration / 1000.0; + osd_message_end = getTimeMs() + duration; } static std::string get_notification() { std::lock_guard lock(osd_message_mutex); - if (!osd_message.empty() && os_GetSeconds() >= osd_message_end) + if (!osd_message.empty() && getTimeMs() >= osd_message_end) osd_message.clear(); return osd_message; } @@ -3348,7 +3348,7 @@ void gui_display_ui() emu.start(); } -static float LastFPSTime; +static u64 LastFPSTime; static int lastFrameCount = 0; static float fps = -1; @@ -3356,9 +3356,9 @@ static std::string getFPSNotification() { if (config::ShowFPS) { - double now = os_GetSeconds(); - if (now - LastFPSTime >= 1.0) { - fps = (MainFrameCount - lastFrameCount) / (now - LastFPSTime); + u64 now = getTimeMs(); + if (now - LastFPSTime >= 1000) { + fps = ((float)MainFrameCount - lastFrameCount) * 1000.f / (now - LastFPSTime); LastFPSTime = now; lastFrameCount = MainFrameCount; } diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index b3846b048..fb9c7d51c 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -47,7 +47,7 @@ static bool gameRunning; static bool mouseCaptured; static std::string clipboardText; static std::string barcode; -static double lastBarcodeTime; +static u64 lastBarcodeTime; static KeyboardLayout detectKeyboardLayout(); static bool handleBarcodeScanner(const SDL_Event& event); @@ -1179,8 +1179,8 @@ static bool handleBarcodeScanner(const SDL_Event& event) return false; } } - double now = os_GetSeconds(); - if (!barcode.empty() && now - lastBarcodeTime >= 0.5) + u64 now = getTimeMs(); + if (!barcode.empty() && now - lastBarcodeTime >= 500) { INFO_LOG(INPUT, "Barcode timeout"); barcode.clear(); diff --git a/core/sdl/sdl_gamepad.h b/core/sdl/sdl_gamepad.h index 3c1a4cd20..60782f738 100644 --- a/core/sdl/sdl_gamepad.h +++ b/core/sdl/sdl_gamepad.h @@ -1,7 +1,7 @@ #pragma once #include "input/gamepad_device.h" #include "input/mouse.h" -#include "oslib/oslib.h" +#include "stdclass.h" #include "sdl.h" template @@ -233,7 +233,7 @@ public: if (rumbleEnabled) { vib_inclination = inclination * power; - vib_stop_time = os_GetSeconds() + duration_ms / 1000.0; + vib_stop_time = getTimeMs() + duration_ms; u16 intensity = getRumbleIntensity(power); SDL_JoystickRumble(sdl_joystick, intensity, intensity, duration_ms); @@ -245,7 +245,7 @@ public: return; if (vib_inclination > 0) { - int rem_time = (vib_stop_time - os_GetSeconds()) * 1000; + int rem_time = vib_stop_time - getTimeMs(); if (rem_time <= 0) vib_inclination = 0; else @@ -418,7 +418,7 @@ public: } protected: - double vib_stop_time = 0; + u64 vib_stop_time = 0; SDL_JoystickID sdl_joystick_instance; private: diff --git a/core/sdl/sdl_keyboard_mac.h b/core/sdl/sdl_keyboard_mac.h index abb53ac97..732f8e8b0 100644 --- a/core/sdl/sdl_keyboard_mac.h +++ b/core/sdl/sdl_keyboard_mac.h @@ -19,6 +19,7 @@ #pragma once #ifdef __APPLE__ #include "sdl_keyboard.h" +#include "stdclass.h" #include #include #include @@ -55,7 +56,7 @@ public: if (!rumbleEnabled) return; - vib_stop_time = os_GetSeconds() + duration_ms / 1000.0; + vib_stop_time = getTimeMs() + duration_ms; __block int pattern; if (power >= 0.75) @@ -82,7 +83,7 @@ public: dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 10 * NSEC_PER_MSEC * rumblePower / 100.f, 0); dispatch_source_set_event_handler(_timer, ^{ - if ( vib_stop_time - os_GetSeconds() < 0 ) + if (vib_stop_time < getTimeMs()) { dispatch_source_cancel(_timer); return; @@ -105,7 +106,7 @@ public: private: std::stack vib_timer_stack; CFTypeRef vib_device = NULL; - double vib_stop_time = 0; + u64 vib_stop_time = 0; }; #endif // _APPLE_ diff --git a/core/stdclass.cpp b/core/stdclass.cpp index 8751941b3..32b26bd3d 100644 --- a/core/stdclass.cpp +++ b/core/stdclass.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #ifdef _WIN32 #include @@ -211,3 +212,12 @@ void RamRegion::free() freeAligned(data); data = nullptr; } + +u64 getTimeMs() +{ + using the_clock = std::chrono::steady_clock; + std::chrono::time_point now = the_clock::now(); + static std::chrono::time_point start = now; + + return std::chrono::duration_cast(now - start).count(); +} diff --git a/core/stdclass.h b/core/stdclass.h index 8b08f0577..d5efa9e82 100644 --- a/core/stdclass.h +++ b/core/stdclass.h @@ -197,3 +197,5 @@ public: return v; } }; + +u64 getTimeMs(); diff --git a/core/types.h b/core/types.h index 21b9ab3d5..239b0c7ea 100644 --- a/core/types.h +++ b/core/types.h @@ -80,9 +80,6 @@ inline static void JITWriteProtect(bool enabled) { #include "log/Log.h" -#define VER_EMUNAME "Flycast" -#define VER_SHORTNAME VER_EMUNAME - #ifndef _MSC_VER #define stricmp strcasecmp #endif diff --git a/core/windows/fault_handler.cpp b/core/windows/fault_handler.cpp index e30715499..ac7ce0903 100644 --- a/core/windows/fault_handler.cpp +++ b/core/windows/fault_handler.cpp @@ -169,18 +169,3 @@ void os_UninstallFaultHandler() #endif SetUnhandledExceptionFilter(prevExceptionHandler); } - -double os_GetSeconds() -{ - static double qpfd = []() { - LARGE_INTEGER qpf; - QueryPerformanceFrequency(&qpf); - return 1.0 / qpf.QuadPart; }(); - - LARGE_INTEGER time_now; - - QueryPerformanceCounter(&time_now); - static LARGE_INTEGER time_now_base = time_now; - - return (time_now.QuadPart - time_now_base.QuadPart) * qpfd; -} diff --git a/shell/android-studio/flycast/src/main/jni/src/android_keyboard.h b/shell/android-studio/flycast/src/main/jni/src/android_keyboard.h index 087af3a5f..f8bc27b83 100644 --- a/shell/android-studio/flycast/src/main/jni/src/android_keyboard.h +++ b/shell/android-studio/flycast/src/main/jni/src/android_keyboard.h @@ -688,8 +688,8 @@ private: return false; } - double now = os_GetSeconds(); - if (!barcode.empty() && now - lastBarcodeTime >= 0.5) + u64 now = getTimeMs(); + if (!barcode.empty() && now - lastBarcodeTime >= 500) { INFO_LOG(INPUT, "Barcode timeout"); barcode.clear(); @@ -721,5 +721,5 @@ private: int modifiers = 0; std::string barcode; - double lastBarcodeTime = 0.0; + u64 lastBarcodeTime = 0; }; diff --git a/shell/apple/emulator-ios/emulator/FlycastViewController.mm b/shell/apple/emulator-ios/emulator/FlycastViewController.mm index 07cd93a42..4d4c66cde 100644 --- a/shell/apple/emulator-ios/emulator/FlycastViewController.mm +++ b/shell/apple/emulator-ios/emulator/FlycastViewController.mm @@ -738,11 +738,11 @@ void pickIosFile() const char *getIosJitStatus() { - static double lastCheckTime; - if (!iosJitAuthorized && os_GetSeconds() - lastCheckTime > 10.0) + static u64 lastCheckTime; + if (!iosJitAuthorized && getTimeMs() - lastCheckTime > 10000) { [flycastViewController altKitStart]; - lastCheckTime = os_GetSeconds(); + lastCheckTime = getTimeMs(); } return iosJitStatus.c_str(); } diff --git a/shell/libretro/libretro.cpp b/shell/libretro/libretro.cpp index d28ba5c7a..78a5a7bbc 100644 --- a/shell/libretro/libretro.cpp +++ b/shell/libretro/libretro.cpp @@ -2502,11 +2502,6 @@ void retro_rend_present() is_dupe = false; } -static uint32_t get_time_ms() -{ - return (uint32_t)(os_GetSeconds() * 1000.0); -} - static void get_analog_stick( retro_input_state_t input_state_cb, int player_index, int stick, @@ -2963,14 +2958,14 @@ static void UpdateInputState(u32 port) } if (rumble.set_rumble_state != NULL && vib_stop_time[port] > 0) { - if (get_time_ms() >= vib_stop_time[port]) + if (getTimeMs() >= vib_stop_time[port]) { vib_stop_time[port] = 0; rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 0); } else if (vib_delta[port] > 0.0) { - u32 rem_time = vib_stop_time[port] - get_time_ms(); + u32 rem_time = vib_stop_time[port] - getTimeMs(); rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, 65535 * vib_strength[port] * rem_time * vib_delta[port]); } } @@ -3378,7 +3373,7 @@ static void updateVibration(u32 port, float power, float inclination, u32 durati vib_strength[port] = power; rumble.set_rumble_state(port, RETRO_RUMBLE_STRONG, (u16)(65535 * power)); - vib_stop_time[port] = get_time_ms() + durationMs; + vib_stop_time[port] = getTimeMs() + durationMs; vib_delta[port] = inclination; } diff --git a/shell/switch/switch_gamepad.h b/shell/switch/switch_gamepad.h index b21c7b96e..e6956eff1 100644 --- a/shell/switch/switch_gamepad.h +++ b/shell/switch/switch_gamepad.h @@ -59,23 +59,23 @@ public: memcpy(&vibValues[1], &vibValues[0], sizeof(HidVibrationValue)); hidSendVibrationValues(getDeviceHandle(), vibValues, 2); if (power != 0.f) - vib_stop_time = os_GetSeconds() + duration_ms / 1000.0; + vib_stop_time = getTimeMs() + duration_ms; else - vib_stop_time = 0.0; + vib_stop_time = 0; } void update_rumble() override { if (!rumbleEnabled || vib_stop_time == 0.0) return; - int rem_time = (vib_stop_time - os_GetSeconds()) * 1000; + int rem_time = vib_stop_time - getTimeMs(); if (rem_time <= 0) { HidVibrationValue vibValues[2]{}; vibValues[0].freq_low = vibValues[1].freq_low = 160.f; vibValues[0].freq_high = vibValues[1].freq_high = 320.f; hidSendVibrationValues(getDeviceHandle(), vibValues, 2); - vib_stop_time = 0.0; + vib_stop_time = 0; } } diff --git a/tests/src/test_stubs.cpp b/tests/src/test_stubs.cpp index 0b6b16001..e87bc1459 100644 --- a/tests/src/test_stubs.cpp +++ b/tests/src/test_stubs.cpp @@ -38,20 +38,3 @@ void os_RunInstance(int argc, const char *argv[]) { } #endif - -#ifdef _WIN32 -#include - -static LARGE_INTEGER qpf; -static double qpfd; -//Helper functions -double os_GetSeconds() -{ - static bool initme = (QueryPerformanceFrequency(&qpf), qpfd=1/(double)qpf.QuadPart); - LARGE_INTEGER time_now; - - QueryPerformanceCounter(&time_now); - static LARGE_INTEGER time_now_base = time_now; - return (time_now.QuadPart - time_now_base.QuadPart)*qpfd; -} -#endif From a136583189c0117ba0c2dd6c028f0767df3b915f Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Thu, 11 Apr 2024 16:27:28 +0200 Subject: [PATCH 44/86] drop dispmanx support. move switch main() to its own file Dispmanx support is provided by SDL Enable omx audio and set TARGET_VIDEOCORE when building for RPi3 --- CMakeLists.txt | 10 +-- core/cfg/option.cpp | 4 - core/cfg/option.h | 4 - core/linux-dist/dispmanx.cpp | 82 --------------------- core/linux-dist/dispmanx.h | 3 - core/linux-dist/main.cpp | 48 +----------- core/wsi/gl_context.h | 2 +- {core/linux => shell/switch}/libnx_vmem.cpp | 0 shell/switch/switch_main.cpp | 81 ++++++++++++++++++++ 9 files changed, 89 insertions(+), 145 deletions(-) delete mode 100644 core/linux-dist/dispmanx.cpp delete mode 100644 core/linux-dist/dispmanx.h rename {core/linux => shell/switch}/libnx_vmem.cpp (100%) create mode 100644 shell/switch/switch_main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 49982f6bc..771eb10c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -528,6 +528,7 @@ if(UNIX AND NOT APPLE AND NOT ANDROID) if(USE_GLES2) target_compile_definitions(${PROJECT_NAME} PRIVATE GLES GLES2) if(USE_VIDEOCORE) + target_compile_definitions(${PROJECT_NAME} PRIVATE TARGET_VIDEOCORE USE_OMX) target_link_libraries(${PROJECT_NAME} PRIVATE "-lbrcmGLESv2") target_link_directories(${PROJECT_NAME} PRIVATE "/opt/vc/lib") endif() @@ -1051,19 +1052,18 @@ else() core/linux/unwind_info.cpp) if(NINTENDO_SWITCH) target_sources(${PROJECT_NAME} PRIVATE - core/linux/libnx_vmem.cpp + shell/switch/libnx_vmem.cpp shell/switch/stubs.c shell/switch/context_switch.S shell/switch/nswitch.h shell/switch/switch_gamepad.h + shell/switch/switch_main.cpp shell/switch/ucontext.h) endif() endif() if(NOT LIBRETRO) target_sources(${PROJECT_NAME} PRIVATE - core/linux-dist/dispmanx.cpp - core/linux-dist/dispmanx.h core/linux-dist/evdev.cpp core/linux-dist/evdev.h core/linux-dist/icon.h @@ -1505,7 +1505,7 @@ endif() if((USE_OPENGL OR USE_GLES2 OR USE_GLES) AND NOT LIBRETRO) add_library(glad STATIC core/deps/glad/src/gl.c) if(NOT APPLE AND NOT WIN32 AND NOT SDL2_FOUND) - # When SDL2 is not found, we can use EGL with ANativeWindow (Android), DispmanX (Raspberry Pi) or X11 + # When SDL2 is not found, we can use EGL with ANativeWindow (Android) or X11 target_sources(glad PRIVATE core/deps/glad/src/egl.c) endif() target_include_directories(glad PUBLIC core/deps/glad/include) @@ -1721,7 +1721,7 @@ if(NOT LIBRETRO) ${CMAKE_CURRENT_BINARY_DIR}/Flycast.app/Contents/Frameworks/libvulkan.dylib) endif() endif() - elseif(UNIX OR NINTENDO_SWITCH) + elseif(UNIX) if(NOT BUILD_TESTING) target_sources(${PROJECT_NAME} PRIVATE core/linux-dist/main.cpp) diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index 20720d33f..e8fa133c8 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -159,10 +159,6 @@ Option NetworkOutput("NetworkOutput", false, "network"); Option MultiboardSlaves("MultiboardSlaves", 1, "network"); Option BattleCableEnable("BattleCable", false, "network"); -#ifdef SUPPORT_DISPMANX -Option DispmanxMaintainAspect("maintain_aspect", true, "dispmanx"); -#endif - #ifdef USE_OMX Option OmxAudioLatency("audio_latency", 100, "omx"); Option OmxAudioHdmi("audio_hdmi", true, "omx"); diff --git a/core/cfg/option.h b/core/cfg/option.h index 05d30b6f2..0890b6260 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -525,10 +525,6 @@ extern Option NetworkOutput; extern Option MultiboardSlaves; extern Option BattleCableEnable; -#ifdef SUPPORT_DISPMANX -extern Option DispmanxMaintainAspect; -#endif - #ifdef USE_OMX extern Option OmxAudioLatency; extern Option OmxAudioHdmi; diff --git a/core/linux-dist/dispmanx.cpp b/core/linux-dist/dispmanx.cpp deleted file mode 100644 index adafdf984..000000000 --- a/core/linux-dist/dispmanx.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#if defined(SUPPORT_DISPMANX) -#include "dispmanx.h" -#include "types.h" -#include "wsi/context.h" -#include "cfg/option.h" - -#include -#include - -void dispmanx_window_create() -{ - DISPMANX_DISPLAY_HANDLE_T dispman_display; - DISPMANX_UPDATE_HANDLE_T dispman_update; - DISPMANX_ELEMENT_HANDLE_T dispman_element; - VC_RECT_T src_rect; - VC_RECT_T dst_rect; - VC_DISPMANX_ALPHA_T dispman_alpha; - uint32_t screen_width; - uint32_t screen_height; - uint32_t window_width; - uint32_t window_height; - - dispman_alpha.flags = DISPMANX_FLAGS_ALPHA_FIXED_ALL_PIXELS; - dispman_alpha.opacity = 0xFF; - dispman_alpha.mask = 0; - - graphics_get_display_size(0 /* LCD */, &screen_width, &screen_height); - - window_width = cfgLoadInt("window", "width", 0); - window_height = cfgLoadInt("window", "height", 0); - - if(window_width < 1) - window_width = screen_width; - if(window_height < 1) - window_height = screen_height; - - src_rect.x = 0; - src_rect.y = 0; - src_rect.width = window_width << 16; - src_rect.height = window_height << 16; - - if (config::DispmanxMaintainAspect) - { - float screen_aspect = (float)screen_width / screen_height; - float window_aspect = (float)window_width / window_height; - if(screen_aspect > window_aspect) - { - dst_rect.width = window_width * screen_height / window_height; - dst_rect.height = screen_height; - } - else - { - dst_rect.width = screen_width; - dst_rect.height = window_height * screen_width / window_width; - } - dst_rect.x = (screen_width - dst_rect.width) / 2; - dst_rect.y = (screen_height - dst_rect.height) / 2; - } - else - { - dst_rect.x = 0; - dst_rect.y = 0; - dst_rect.width = screen_width; - dst_rect.height = screen_height; - } - - dispman_display = vc_dispmanx_display_open( 0 /* LCD */); - dispman_update = vc_dispmanx_update_start( 0 ); - dispman_element = vc_dispmanx_element_add(dispman_update, dispman_display, - 0 /*layer*/, &dst_rect, 0 /*src*/, - &src_rect, DISPMANX_PROTECTION_NONE, - &dispman_alpha /*alpha*/, 0 /*clamp*/, (DISPMANX_TRANSFORM_T)0 /*transform*/); - - static EGL_DISPMANX_WINDOW_T native_window; - native_window.element = dispman_element; - native_window.width = window_width; - native_window.height = window_height; - vc_dispmanx_update_submit_sync( dispman_update ); - - initRenderApi(&native_window, (void *)dispman_display); -} -#endif diff --git a/core/linux-dist/dispmanx.h b/core/linux-dist/dispmanx.h deleted file mode 100644 index db95adfa8..000000000 --- a/core/linux-dist/dispmanx.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -extern void dispmanx_window_create(); diff --git a/core/linux-dist/main.cpp b/core/linux-dist/main.cpp index e970f880a..456f150e0 100644 --- a/core/linux-dist/main.cpp +++ b/core/linux-dist/main.cpp @@ -3,7 +3,7 @@ #endif #include "types.h" -#if defined(__unix__) || defined(__SWITCH__) +#if defined(__unix__) #include "log/LogManager.h" #include "emulator.h" #include "rend/mainui.h" @@ -16,14 +16,6 @@ #include #include -#if defined(__SWITCH__) -#include "nswitch.h" -#endif - -#if defined(SUPPORT_DISPMANX) - #include "dispmanx.h" -#endif - #if defined(SUPPORT_X11) #include "x11.h" #endif @@ -96,9 +88,6 @@ void os_SetWindowText(const char * text) void os_CreateWindow() { - #if defined(SUPPORT_DISPMANX) - dispmanx_window_create(); - #endif #if defined(SUPPORT_X11) x11_window_create(); #endif @@ -113,10 +102,6 @@ void common_linux_setup(); // $HOME/.config/flycast on linux std::string find_user_config_dir() { -#ifdef __SWITCH__ - flycast::mkdir("/flycast", 0755); - return "/flycast/"; -#else std::string xdg_home; if (nowide::getenv("XDG_CONFIG_HOME") != nullptr) // If XDG_CONFIG_HOME is set explicitly, we'll use that instead of $HOME/.config @@ -140,17 +125,12 @@ std::string find_user_config_dir() } // Unable to detect config dir, use the current folder return "."; -#endif } // Find the user data directory. // $HOME/.local/share/flycast on linux std::string find_user_data_dir() { -#ifdef __SWITCH__ - flycast::mkdir("/flycast/data", 0755); - return "/flycast/data/"; -#else std::string xdg_home; if (nowide::getenv("XDG_DATA_HOME") != nullptr) // If XDG_DATA_HOME is set explicitly, we'll use that instead of $HOME/.local/share @@ -174,10 +154,8 @@ std::string find_user_data_dir() } // Unable to detect data dir, use the current folder return "."; -#endif } -#ifndef __SWITCH__ static void addDirectoriesFromPath(std::vector& dirs, const std::string& path, const std::string& suffix) { std::string::size_type pos = 0; @@ -193,7 +171,6 @@ static void addDirectoriesFromPath(std::vector& dirs, const std::st if (pos < path.length()) dirs.push_back(path.substr(pos) + suffix); } -#endif // Find a file in the user and system config directories. // The following folders are checked in this order: @@ -208,9 +185,6 @@ std::vector find_system_config_dirs() { std::vector dirs; -#ifdef __SWITCH__ - dirs.push_back("/flycast/"); -#else std::string xdg_home; if (nowide::getenv("XDG_CONFIG_HOME") != nullptr) // If XDG_CONFIG_HOME is set explicitly, we'll use that instead of $HOME/.config @@ -235,7 +209,6 @@ std::vector find_system_config_dirs() dirs.push_back("/etc/flycast/"); // This isn't part of the XDG spec, but much more common than /etc/xdg/ dirs.push_back("/etc/xdg/flycast/"); } -#endif dirs.push_back("./"); return dirs; @@ -256,9 +229,6 @@ std::vector find_system_data_dirs() { std::vector dirs; -#ifdef __SWITCH__ - dirs.push_back("/flycast/data/"); -#else std::string xdg_home; if (nowide::getenv("XDG_DATA_HOME") != nullptr) // If XDG_DATA_HOME is set explicitly, we'll use that instead of $HOME/.local/share @@ -288,7 +258,6 @@ std::vector find_system_data_dirs() std::string path = (std::string)nowide::getenv("FLYCAST_BIOS_PATH"); addDirectoriesFromPath(dirs, path, "/"); } -#endif dirs.push_back("./"); dirs.push_back("data/"); @@ -323,11 +292,6 @@ static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, int main(int argc, char* argv[]) { selfPath = argv[0]; -#if defined(__SWITCH__) - socketInitializeDefault(); - nxlinkStdio(); - //appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend); -#endif #if defined(USE_BREAKPAD) google_breakpad::MinidumpDescriptor descriptor("/tmp"); google_breakpad::ExceptionHandler eh(descriptor, nullptr, dumpCallback, nullptr, true, -1); @@ -353,9 +317,7 @@ int main(int argc, char* argv[]) } #endif -#if defined(__unix__) common_linux_setup(); -#endif if (flycast_init(argc, argv)) die("Flycast initialization failed\n"); @@ -376,19 +338,13 @@ int main(int argc, char* argv[]) flycast_term(); os_UninstallFaultHandler(); -#if defined(__SWITCH__) - socketExit(); -#endif - return 0; } -#if defined(__unix__) [[noreturn]] void os_DebugBreak() { raise(SIGTRAP); std::abort(); } -#endif -#endif // __unix__ || __SWITCH__ +#endif // __unix__ diff --git a/core/wsi/gl_context.h b/core/wsi/gl_context.h index 1858322ef..bb7236cfe 100644 --- a/core/wsi/gl_context.h +++ b/core/wsi/gl_context.h @@ -88,7 +88,7 @@ private: #include "sdl.h" -#elif defined(__ANDROID__) || defined(SUPPORT_DISPMANX) || defined(SUPPORT_X11) +#elif defined(__ANDROID__) || defined(SUPPORT_X11) #include "egl.h" diff --git a/core/linux/libnx_vmem.cpp b/shell/switch/libnx_vmem.cpp similarity index 100% rename from core/linux/libnx_vmem.cpp rename to shell/switch/libnx_vmem.cpp diff --git a/shell/switch/switch_main.cpp b/shell/switch/switch_main.cpp new file mode 100644 index 000000000..ecec83800 --- /dev/null +++ b/shell/switch/switch_main.cpp @@ -0,0 +1,81 @@ +/* + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . + */ +#include "nswitch.h" +#include "stdclass.h" +#include "sdl/sdl.h" +#include "log/LogManager.h" +#include "emulator.h" +#include "rend/mainui.h" +#include "oslib/directory.h" + +int main(int argc, char *argv[]) +{ + socketInitializeDefault(); + nxlinkStdio(); + //appletSetFocusHandlingMode(AppletFocusHandlingMode_NoSuspend); + + LogManager::Init(); + + // Set directories + flycast::mkdir("/flycast", 0755); + flycast::mkdir("/flycast/data", 0755); + set_user_config_dir("/flycast/"); + set_user_data_dir("/flycast/data/"); + + add_system_config_dir("/flycast"); + add_system_config_dir("./"); + add_system_data_dir("/flycast/data/"); + add_system_data_dir("./"); + add_system_data_dir("data/"); + + if (flycast_init(argc, argv)) + die("Flycast initialization failed"); + + mainui_loop(); + + sdl_window_destroy(); + flycast_term(); + + + socketExit(); + + return 0; +} + +void os_SetupInput() +{ + input_sdl_init(); +} + +void os_TermInput() +{ + input_sdl_quit(); +} + +void UpdateInputState() +{ + input_sdl_handle(); +} + +void os_DoEvents() +{ +} + +void os_CreateWindow() +{ + sdl_window_create(); +} From b3781618376e76a98ead763829813b124607a54c Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Thu, 11 Apr 2024 18:50:49 +0200 Subject: [PATCH 45/86] lr: switch build fix --- shell/switch/switch_main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/shell/switch/switch_main.cpp b/shell/switch/switch_main.cpp index ecec83800..bf59e3173 100644 --- a/shell/switch/switch_main.cpp +++ b/shell/switch/switch_main.cpp @@ -14,6 +14,7 @@ You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ +#ifndef LIBRETRO #include "nswitch.h" #include "stdclass.h" #include "sdl/sdl.h" @@ -50,7 +51,6 @@ int main(int argc, char *argv[]) sdl_window_destroy(); flycast_term(); - socketExit(); return 0; @@ -79,3 +79,5 @@ void os_CreateWindow() { sdl_window_create(); } + +#endif //!LIBRETRO From 336706e72876734a5e1e27a04823f73a7413632a Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Fri, 12 Apr 2024 17:37:45 +0200 Subject: [PATCH 46/86] move most os_* funcs to oslib add os_DestroyWindow and os_UpdateInputState --- core/linux-dist/main.cpp | 75 +----------------- core/linux-dist/x11.cpp | 4 +- core/linux-dist/x11.h | 11 ++- core/linux/common.cpp | 4 +- core/network/ggpo.cpp | 7 +- core/nullDC.cpp | 1 + core/oslib/oslib.cpp | 78 +++++++++++++++++++ core/oslib/oslib.h | 4 +- core/rend/gui_util.h | 2 + core/rend/mainui.cpp | 4 +- core/sdl/sdl.cpp | 6 -- core/sdl/sdl.h | 1 - core/windows/winmain.cpp | 41 ---------- .../flycast/src/main/jni/src/Android.cpp | 19 ----- .../emulator-ios/emulator/AppDelegate.mm | 7 ++ .../emulator/FlycastViewController.mm | 4 +- shell/apple/emulator-ios/emulator/ios_main.mm | 20 ----- .../emulator-osx/emulator-osx/osx-main.mm | 57 ++++---------- shell/libretro/libretro.cpp | 7 +- shell/switch/switch_main.cpp | 22 ------ tests/src/test_stubs.cpp | 15 ---- 21 files changed, 125 insertions(+), 264 deletions(-) diff --git a/core/linux-dist/main.cpp b/core/linux-dist/main.cpp index 456f150e0..26f19aaf1 100644 --- a/core/linux-dist/main.cpp +++ b/core/linux-dist/main.cpp @@ -24,50 +24,10 @@ #include "sdl/sdl.h" #endif -#if defined(USE_EVDEV) - #include "evdev.h" -#endif - #ifdef USE_BREAKPAD #include "breakpad/client/linux/handler/exception_handler.h" #endif -void os_SetupInput() -{ -#if defined(USE_EVDEV) - input_evdev_init(); -#endif - -#if defined(SUPPORT_X11) - input_x11_init(); -#endif - -#if defined(USE_SDL) - input_sdl_init(); -#endif -} - -void os_TermInput() -{ -#if defined(USE_EVDEV) - input_evdev_close(); -#endif -#if defined(USE_SDL) - input_sdl_quit(); -#endif -} - -void UpdateInputState() -{ - #if defined(USE_EVDEV) - input_evdev_handle(); - #endif - - #if defined(USE_SDL) - input_sdl_handle(); - #endif -} - void os_DoEvents() { #if defined(SUPPORT_X11) @@ -76,31 +36,11 @@ void os_DoEvents() #endif } -void os_SetWindowText(const char * text) -{ - #if defined(SUPPORT_X11) - x11_window_set_text(text); - #endif - #if defined(USE_SDL) - sdl_window_set_text(text); - #endif -} - -void os_CreateWindow() -{ - #if defined(SUPPORT_X11) - x11_window_create(); - #endif - #if defined(USE_SDL) - sdl_window_create(); - #endif -} - void common_linux_setup(); // Find the user config directory. // $HOME/.config/flycast on linux -std::string find_user_config_dir() +static std::string find_user_config_dir() { std::string xdg_home; if (nowide::getenv("XDG_CONFIG_HOME") != nullptr) @@ -129,7 +69,7 @@ std::string find_user_config_dir() // Find the user data directory. // $HOME/.local/share/flycast on linux -std::string find_user_data_dir() +static std::string find_user_data_dir() { std::string xdg_home; if (nowide::getenv("XDG_DATA_HOME") != nullptr) @@ -181,7 +121,7 @@ static void addDirectoriesFromPath(std::vector& dirs, const std::st // /etc/flycast/ // /etc/xdg/flycast/ // . -std::vector find_system_config_dirs() +static std::vector find_system_config_dirs() { std::vector dirs; @@ -225,7 +165,7 @@ std::vector find_system_config_dirs() // <$FLYCAST_BIOS_PATH> // ./ // ./data -std::vector find_system_data_dirs() +static std::vector find_system_data_dirs() { std::vector dirs; @@ -328,13 +268,6 @@ int main(int argc, char* argv[]) mainui_loop(); -#if defined(SUPPORT_X11) - x11_window_destroy(); -#endif -#if defined(USE_SDL) - sdl_window_destroy(); -#endif - flycast_term(); os_UninstallFaultHandler(); diff --git a/core/linux-dist/x11.cpp b/core/linux-dist/x11.cpp index f80c1febb..a11d20a54 100644 --- a/core/linux-dist/x11.cpp +++ b/core/linux-dist/x11.cpp @@ -21,6 +21,8 @@ #define DEFAULT_WINDOW_WIDTH 640 #define DEFAULT_WINDOW_HEIGHT 480 +static void x11_window_set_text(const char *text); + static Window x11_win; Display *x11_disp; @@ -356,7 +358,7 @@ void x11_window_create() } } -void x11_window_set_text(const char* text) +static void x11_window_set_text(const char* text) { if (x11_win) { diff --git a/core/linux-dist/x11.h b/core/linux-dist/x11.h index 75c176531..7013db703 100644 --- a/core/linux-dist/x11.h +++ b/core/linux-dist/x11.h @@ -1,11 +1,10 @@ #pragma once -extern void input_x11_init(); -extern void event_x11_handle(); -extern void input_x11_handle(); -extern void x11_window_create(); -extern void x11_window_set_text(const char* text); -extern void x11_window_destroy(); +void input_x11_init(); +void event_x11_handle(); +void input_x11_handle(); +void x11_window_create(); +void x11_window_destroy(); // numbers const int KEY_1 = 10; diff --git a/core/linux/common.cpp b/core/linux/common.cpp index 00453b762..0ca150af0 100644 --- a/core/linux/common.cpp +++ b/core/linux/common.cpp @@ -162,7 +162,7 @@ static void linux_fix_personality() #endif } -#if defined(__unix__) && !defined(LIBRETRO) +#if defined(__unix__) && !defined(LIBRETRO) && !defined(__ANDROID__) static void sigintHandler(int) { dc_exit(); @@ -175,7 +175,7 @@ void common_linux_setup() enable_runfast(); os_InstallFaultHandler(); -#if defined(__unix__) && !defined(LIBRETRO) +#if defined(__unix__) && !defined(LIBRETRO) && !defined(__ANDROID__) // exit cleanly on ^C signal(SIGINT, sigintHandler); #endif diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp index 40777bb22..dbcb325a2 100644 --- a/core/network/ggpo.cpp +++ b/core/network/ggpo.cpp @@ -23,10 +23,9 @@ #include "input/keyboard_device.h" #include "input/mouse.h" #include "cfg/option.h" +#include "oslib/oslib.h" #include -void UpdateInputState(); - namespace ggpo { @@ -35,7 +34,7 @@ bool inRollback; static void getLocalInput(MapleInputState inputState[4]) { if (!config::ThreadedRendering) - UpdateInputState(); + os_UpdateInputState(); std::lock_guard lock(relPosMutex); for (int player = 0; player < 4; player++) { @@ -706,7 +705,7 @@ bool nextFrame() // may call save_game_state do { if (!config::ThreadedRendering) - UpdateInputState(); + os_UpdateInputState(); Inputs inputs; inputs.kcode = ~kcode[0]; if (rt[0] >= 0x4000) diff --git a/core/nullDC.cpp b/core/nullDC.cpp index 15d2f3e8b..ef0a5b8d3 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -83,6 +83,7 @@ void SaveSettings() void flycast_term() { + os_DestroyWindow(); gui_cancel_load(); lua::term(); emu.term(); diff --git a/core/oslib/oslib.cpp b/core/oslib/oslib.cpp index 3a078bae7..38e9d0d30 100644 --- a/core/oslib/oslib.cpp +++ b/core/oslib/oslib.cpp @@ -25,6 +25,20 @@ #ifndef _WIN32 #include #endif +#if defined(USE_SDL) +#include "sdl/sdl.h" +#else + #if defined(SUPPORT_X11) + #include "linux-dist/x11.h" + #endif + #if defined(USE_EVDEV) + #include "linux-dist/evdev.h" + #endif +#endif +#if defined(_WIN32) && !defined(TARGET_UWP) +#include "windows/rawinput.h" +#endif +#include "profiler/fc_profiler.h" namespace hostfs { @@ -139,6 +153,70 @@ std::string getTextureDumpPath() } +void os_CreateWindow() +{ +#if defined(USE_SDL) + sdl_window_create(); +#elif defined(SUPPORT_X11) + x11_window_create(); +#endif +} + +void os_DestroyWindow() +{ +#if defined(USE_SDL) + sdl_window_destroy(); +#elif defined(SUPPORT_X11) + x11_window_destroy(); +#endif +} + +void os_SetupInput() +{ +#if defined(USE_SDL) + input_sdl_init(); +#else + #if defined(SUPPORT_X11) + input_x11_init(); + #endif + #if defined(USE_EVDEV) + input_evdev_init(); + #endif +#endif +#if defined(_WIN32) && !defined(TARGET_UWP) + if (config::UseRawInput) + rawinput::init(); +#endif +} + +void os_TermInput() +{ +#if defined(USE_SDL) + input_sdl_quit(); +#else + #if defined(USE_EVDEV) + input_evdev_close(); + #endif +#endif +#if defined(_WIN32) && !defined(TARGET_UWP) + if (config::UseRawInput) + rawinput::term(); +#endif +} + +void os_UpdateInputState() +{ + FC_PROFILE_SCOPE; + +#if defined(USE_SDL) + input_sdl_handle(); +#else + #if defined(USE_EVDEV) + input_evdev_handle(); + #endif +#endif +} + #ifdef USE_BREAKPAD #include "rend/boxart/http_client.h" diff --git a/core/oslib/oslib.h b/core/oslib/oslib.h index 91aff2106..8744af19e 100644 --- a/core/oslib/oslib.h +++ b/core/oslib/oslib.h @@ -4,12 +4,12 @@ #include #endif -void os_SetWindowText(const char* text); - void os_DoEvents(); void os_CreateWindow(); +void os_DestroyWindow(); void os_SetupInput(); void os_TermInput(); +void os_UpdateInputState(); void os_InstallFaultHandler(); void os_UninstallFaultHandler(); void os_RunInstance(int argc, const char *argv[]); diff --git a/core/rend/gui_util.h b/core/rend/gui_util.h index 931c07bd9..fc25f3f52 100644 --- a/core/rend/gui_util.h +++ b/core/rend/gui_util.h @@ -24,6 +24,7 @@ #include "imgui_internal.h" #include "gui.h" #include "emulator.h" +#include "oslib/oslib.h" #include #include @@ -78,6 +79,7 @@ public: { progress.reset(); future = std::async(std::launch::async, [this, path] { + ThreadName _("GameLoader"); emu.loadGame(path.c_str(), &progress); }); } diff --git a/core/rend/mainui.cpp b/core/rend/mainui.cpp index c2dce96df..8191679e4 100644 --- a/core/rend/mainui.cpp +++ b/core/rend/mainui.cpp @@ -34,14 +34,12 @@ static bool mainui_enabled; u32 MainFrameCount; static bool forceReinit; -void UpdateInputState(); - bool mainui_rend_frame() { FC_PROFILE_SCOPE; os_DoEvents(); - UpdateInputState(); + os_UpdateInputState(); if (gui_is_open() || gui_state == GuiState::VJoyEdit) { diff --git a/core/sdl/sdl.cpp b/core/sdl/sdl.cpp index fb9c7d51c..48b8259f7 100644 --- a/core/sdl/sdl.cpp +++ b/core/sdl/sdl.cpp @@ -562,12 +562,6 @@ void input_sdl_handle() } } -void sdl_window_set_text(const char* text) -{ - if (window != nullptr) - SDL_SetWindowTitle(window, text); -} - static float hdpiScaling = 1.f; static inline void get_window_state() diff --git a/core/sdl/sdl.h b/core/sdl/sdl.h index a79f85f22..c60d083f2 100644 --- a/core/sdl/sdl.h +++ b/core/sdl/sdl.h @@ -6,7 +6,6 @@ void input_sdl_init(); void input_sdl_handle(); void input_sdl_quit(); void sdl_window_create(); -void sdl_window_set_text(const char* text); void sdl_window_destroy(); bool sdl_recreate_window(u32 flags); void sdl_fix_steamdeck_dpi(SDL_Window *window); diff --git a/core/windows/winmain.cpp b/core/windows/winmain.cpp index f39b51e90..302e07dfd 100644 --- a/core/windows/winmain.cpp +++ b/core/windows/winmain.cpp @@ -28,8 +28,6 @@ #include #include "cfg/option.h" #include "rend/gui.h" -#else -#include "rawinput.h" #endif #include "oslib/oslib.h" #include "stdclass.h" @@ -51,26 +49,6 @@ #include #include -void os_SetupInput() -{ - input_sdl_init(); - -#ifndef TARGET_UWP - if (config::UseRawInput) - rawinput::init(); -#endif -} - -void os_TermInput() -{ - input_sdl_quit(); - -#ifndef TARGET_UWP - if (config::UseRawInput) - rawinput::term(); -#endif -} - static void setupPath() { #ifndef TARGET_UWP @@ -110,23 +88,6 @@ static void setupPath() #endif } -void UpdateInputState() -{ - FC_PROFILE_SCOPE; - - input_sdl_handle(); -} - -void os_CreateWindow() -{ - sdl_window_create(); -} - -void os_SetWindowText(const char* text) -{ - sdl_window_set_text(text); -} - static void reserveBottomMemory() { #if defined(_WIN64) && defined(_DEBUG) @@ -433,8 +394,6 @@ int main(int argc, char* argv[]) mainui_loop(); - sdl_window_destroy(); - flycast_term(); os_UninstallFaultHandler(); diff --git a/shell/android-studio/flycast/src/main/jni/src/Android.cpp b/shell/android-studio/flycast/src/main/jni/src/Android.cpp index e07870fcd..351f6c564 100644 --- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp @@ -99,27 +99,8 @@ void os_DoEvents() { } -void os_CreateWindow() -{ -} - -void UpdateInputState() -{ -} - void common_linux_setup(); -void os_SetupInput() -{ -} -void os_TermInput() -{ -} - -void os_SetWindowText(char const *Text) -{ -} - #if defined(USE_BREAKPAD) static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { diff --git a/shell/apple/emulator-ios/emulator/AppDelegate.mm b/shell/apple/emulator-ios/emulator/AppDelegate.mm index cc568b84f..dc5760173 100644 --- a/shell/apple/emulator-ios/emulator/AppDelegate.mm +++ b/shell/apple/emulator-ios/emulator/AppDelegate.mm @@ -23,6 +23,8 @@ #import "AppDelegate.h" #import +#include +#include #include "emulator.h" #include "log/LogManager.h" #include "cfg/option.h" @@ -50,6 +52,11 @@ static bool emulatorRunning; if (error != nil) NSLog(@"AVAudioSession.setActive: %@", error); + if (getppid() != 1) { + /* Make LLDB ignore EXC_BAD_ACCESS for debugging */ + task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, 0); + } + return YES; } diff --git a/shell/apple/emulator-ios/emulator/FlycastViewController.mm b/shell/apple/emulator-ios/emulator/FlycastViewController.mm index 4d4c66cde..018c261a9 100644 --- a/shell/apple/emulator-ios/emulator/FlycastViewController.mm +++ b/shell/apple/emulator-ios/emulator/FlycastViewController.mm @@ -54,8 +54,6 @@ std::map> IOSGamepad::controllers; std::map> IOSKeyboard::keyboards; std::map> IOSMouse::mice; -void common_linux_setup(); - static bool lockedPointer; static void updatePointerLock(Event event, void *) { @@ -210,7 +208,7 @@ static void updateAudioSession(Event event, void *) } #endif - common_linux_setup(); + os_InstallFaultHandler(); flycast_init(0, nullptr); config::ContentPath.get().clear(); diff --git a/shell/apple/emulator-ios/emulator/ios_main.mm b/shell/apple/emulator-ios/emulator/ios_main.mm index a602d3e07..0fe36432c 100644 --- a/shell/apple/emulator-ios/emulator/ios_main.mm +++ b/shell/apple/emulator-ios/emulator/ios_main.mm @@ -20,8 +20,6 @@ #import #include -#include -#include int darw_printf(const char* text,...) { @@ -40,24 +38,6 @@ int darw_printf(const char* text,...) void os_DoEvents() { } -void os_SetWindowText(const char* t) { -} - -void os_CreateWindow() { - if (getppid() != 1) { - /* Make LLDB ignore EXC_BAD_ACCESS for debugging */ - task_set_exception_ports(mach_task_self(), EXC_MASK_BAD_ACCESS, MACH_PORT_NULL, EXCEPTION_DEFAULT, 0); - } -} - -void UpdateInputState() { -} - -void os_SetupInput() { -} -void os_TermInput() { -} - std::string os_Locale(){ return [[[NSLocale preferredLanguages] objectAtIndex:0] UTF8String]; } diff --git a/shell/apple/emulator-osx/emulator-osx/osx-main.mm b/shell/apple/emulator-osx/emulator-osx/osx-main.mm index 6b3e34ab3..bf5eb401f 100644 --- a/shell/apple/emulator-osx/emulator-osx/osx-main.mm +++ b/shell/apple/emulator-osx/emulator-osx/osx-main.mm @@ -47,10 +47,6 @@ int darw_printf(const char* text, ...) return 0; } -void os_SetWindowText(const char * text) { - puts(text); -} - void os_DoEvents() { #if defined(USE_SDL) NSMenuItem *editMenuItem = [[NSApp mainMenu] itemAtIndex:1]; @@ -58,43 +54,6 @@ void os_DoEvents() { #endif } -void UpdateInputState() { -#if defined(USE_SDL) - input_sdl_handle(); -#endif -} - -void os_CreateWindow() { -#ifdef DEBUG - int ret = task_set_exception_ports( - mach_task_self(), - EXC_MASK_BAD_ACCESS, - MACH_PORT_NULL, - EXCEPTION_DEFAULT, - 0); - - if (ret != KERN_SUCCESS) { - printf("task_set_exception_ports: %s\n", mach_error_string(ret)); - } -#endif - sdl_window_create(); -} - -void os_SetupInput() -{ -#if defined(USE_SDL) - input_sdl_init(); -#endif -} - -void os_TermInput() -{ -#if defined(USE_SDL) - input_sdl_quit(); -#endif -} - -void common_linux_setup(); static int emu_flycast_init(); static void emu_flycast_term() @@ -172,7 +131,6 @@ extern "C" int SDL_main(int argc, char *argv[]) mainui_loop(); - sdl_window_destroy(); emu_flycast_term(); os_UninstallFaultHandler(); @@ -182,7 +140,7 @@ extern "C" int SDL_main(int argc, char *argv[]) static int emu_flycast_init() { LogManager::Init(); - common_linux_setup(); + os_InstallFaultHandler(); NSArray *arguments = [[NSProcessInfo processInfo] arguments]; unsigned long argc = [arguments count]; char **argv = (char **)malloc(argc * sizeof(char*)); @@ -201,6 +159,19 @@ static int emu_flycast_init() for (unsigned long i = 0; i < paramCount; i++) free(argv[i]); free(argv); + +#if defined(DEBUG) || defined(DEBUGFAST) + int ret = task_set_exception_ports( + mach_task_self(), + EXC_MASK_BAD_ACCESS, + MACH_PORT_NULL, + EXCEPTION_DEFAULT, + 0); + + if (ret != KERN_SUCCESS) { + printf("task_set_exception_ports: %s\n", mach_error_string(ret)); + } +#endif return rc; } diff --git a/shell/libretro/libretro.cpp b/shell/libretro/libretro.cpp index 78a5a7bbc..472401d79 100644 --- a/shell/libretro/libretro.cpp +++ b/shell/libretro/libretro.cpp @@ -221,7 +221,6 @@ static std::vector disk_paths; static std::vector disk_labels; static bool disc_tray_open = false; -void UpdateInputState(); static bool set_variable_visibility(void); void retro_set_video_refresh(retro_video_refresh_t cb) @@ -1170,7 +1169,7 @@ void retro_run() emu.start(); poll_cb(); - UpdateInputState(); + os_UpdateInputState(); bool fastforward = false; if (environ_cb(RETRO_ENVIRONMENT_GET_FASTFORWARDING, &fastforward)) settings.input.fastForwardMode = fastforward; @@ -3357,7 +3356,7 @@ static void UpdateInputState(u32 port) } } -void UpdateInputState() +void os_UpdateInputState() { UpdateInputState(0); UpdateInputState(1); @@ -3710,5 +3709,3 @@ void gui_display_notification(const char *msg, int duration) retromsg.frames = duration / 17; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &retromsg); } - -void os_RunInstance(int argc, const char *argv[]) { } diff --git a/shell/switch/switch_main.cpp b/shell/switch/switch_main.cpp index bf59e3173..1c3fdcffe 100644 --- a/shell/switch/switch_main.cpp +++ b/shell/switch/switch_main.cpp @@ -17,7 +17,6 @@ #ifndef LIBRETRO #include "nswitch.h" #include "stdclass.h" -#include "sdl/sdl.h" #include "log/LogManager.h" #include "emulator.h" #include "rend/mainui.h" @@ -48,7 +47,6 @@ int main(int argc, char *argv[]) mainui_loop(); - sdl_window_destroy(); flycast_term(); socketExit(); @@ -56,28 +54,8 @@ int main(int argc, char *argv[]) return 0; } -void os_SetupInput() -{ - input_sdl_init(); -} - -void os_TermInput() -{ - input_sdl_quit(); -} - -void UpdateInputState() -{ - input_sdl_handle(); -} - void os_DoEvents() { } -void os_CreateWindow() -{ - sdl_window_create(); -} - #endif //!LIBRETRO diff --git a/tests/src/test_stubs.cpp b/tests/src/test_stubs.cpp index e87bc1459..aa3793ace 100644 --- a/tests/src/test_stubs.cpp +++ b/tests/src/test_stubs.cpp @@ -15,25 +15,10 @@ HWND getNativeHwnd() } #endif -void os_SetupInput() -{ -} -void os_TermInput() -{ -} - -void UpdateInputState() -{ -} - void os_DoEvents() { } -void os_CreateWindow() -{ -} - void os_RunInstance(int argc, const char *argv[]) { } From de6a43bd2104c84d3909379362663ac1ed57b52e Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 16 Apr 2024 09:40:55 +0200 Subject: [PATCH 47/86] savestate: drop support for legacy libretro and > VREG; deser >> ARMRST; deser >> rtc_EN; - if (deser.version() >= Deserializer::V9) - deser >> RealTimeClock; + deser >> RealTimeClock; deser >> aica_reg; diff --git a/core/hw/flashrom/nvmem.cpp b/core/hw/flashrom/nvmem.cpp index d6c61b02a..e6cfa0d50 100644 --- a/core/hw/flashrom/nvmem.cpp +++ b/core/hw/flashrom/nvmem.cpp @@ -76,8 +76,7 @@ static void add_isp_to_nvmem(DCFlashChip *flash) for (u32 i = FLASH_USER_INET + 5; i <= 0xbf; i++) flash->WriteBlock(FLASH_PT_USER, i, block); - flash_isp1_block isp1; - memset(&isp1, 0, sizeof(isp1)); + flash_isp1_block isp1{}; isp1._unknown[3] = 1; memcpy(isp1.sega, "SEGA", 4); strcpy(isp1.username, "flycast1"); @@ -96,8 +95,7 @@ static void add_isp_to_nvmem(DCFlashChip *flash) block[60] = 1; flash->WriteBlock(FLASH_PT_USER, FLASH_USER_ISP1 + 5, block); - flash_isp2_block isp2; - memset(&isp2, 0, sizeof(isp2)); + flash_isp2_block isp2{}; memcpy(isp2.sega, "SEGA", 4); strcpy(isp2.username, "flycast2"); strcpy(isp2.password, "password"); @@ -373,36 +371,8 @@ void serialize(Serializer& ser) void deserialize(Deserializer& deser) { - if (deser.version() <= Deserializer::VLAST_LIBRETRO) - { - deser.skip(); // size - deser.skip(); // mask - - // Legacy libretro savestate - if (settings.platform.isArcade()) - sys_nvmem->Deserialize(deser); - - deser.skip(); // sys_nvmem/sys_rom->size - deser.skip(); // sys_nvmem/sys_rom->mask - if (settings.platform.isConsole()) - { - sys_nvmem->Deserialize(deser); - } - else if (settings.platform.isAtomiswave()) - { - deser >> static_cast(sys_rom)->state; - deser.deserialize(sys_rom->data, sys_rom->size); - } - else - { - deser.skip(); - } - } - else - { - sys_rom->Deserialize(deser); - sys_nvmem->Deserialize(deser); - } + sys_rom->Deserialize(deser); + sys_nvmem->Deserialize(deser); } } diff --git a/core/hw/gdrom/gdromv3.cpp b/core/hw/gdrom/gdromv3.cpp index a32b4f527..1a29572ea 100644 --- a/core/hw/gdrom/gdromv3.cpp +++ b/core/hw/gdrom/gdromv3.cpp @@ -1412,8 +1412,6 @@ void deserialize(Deserializer& deser) deser.skip(Deserializer::V44); // set_mode_offset (repeat) deser >> ata_cmd; deser >> cdda; - if (deser.version() < Deserializer::V10) - cdda.status = (bool)cdda.status ? cdda_t::Playing : cdda_t::NoInfo; deser >> gd_state; deser >> gd_disk_type; deser >> data_write_mode; @@ -1426,8 +1424,6 @@ void deserialize(Deserializer& deser) deser >> SecNumber; deser >> GDStatus; deser >> ByteCount; - if (deser.version() <= Deserializer::VLAST_LIBRETRO) - deser.skip(); // GDROM_TICK } } diff --git a/core/hw/holly/sb.cpp b/core/hw/holly/sb.cpp index 12e4c5a8c..49e47b1df 100644 --- a/core/hw/holly/sb.cpp +++ b/core/hw/holly/sb.cpp @@ -704,18 +704,7 @@ void sb_serialize(Serializer& ser) void sb_deserialize(Deserializer& deser) { - if (deser.version() <= Deserializer::VLAST_LIBRETRO) - { - for (u32& reg : sb_regs) - { - deser.skip(); // regs.data[i].flags - deser >> reg; - } - } - else - { - deser >> sb_regs; - } + deser >> sb_regs; if (deser.version() < Deserializer::V33) deser >> SB_ISTNRM; if (deser.version() >= Deserializer::V24) @@ -729,9 +718,6 @@ void sb_deserialize(Deserializer& deser) deser.skip(); // SB_FFST_rc; deser.skip(); // SB_FFST; } - if (deser.version() >= Deserializer::V15) - deser >> SB_ADST; - else - SB_ADST = 0; + deser >> SB_ADST; } } diff --git a/core/hw/maple/maple_devs.h b/core/hw/maple/maple_devs.h index 284d11a7c..7f97e5af4 100755 --- a/core/hw/maple/maple_devs.h +++ b/core/hw/maple/maple_devs.h @@ -142,8 +142,7 @@ struct maple_device ser << player_num; } virtual void deserialize(Deserializer& deser) { - if (deser.version() >= Deserializer::V14) - deser >> player_num; + deser >> player_num; } virtual MapleDeviceType get_device_type() = 0; diff --git a/core/hw/naomi/naomi.cpp b/core/hw/naomi/naomi.cpp index 8e51c5993..36b705eb0 100644 --- a/core/hw/naomi/naomi.cpp +++ b/core/hw/naomi/naomi.cpp @@ -409,10 +409,7 @@ void naomi_Deserialize(Deserializer& deser) deser.skip(); // reg_dimm_parameterh; deser.skip(); // reg_dimm_status; } - if (deser.version() < Deserializer::V11) - deser.skip(); - else if (deser.version() >= Deserializer::V14) - deser >> aw_maple_devs; + deser >> aw_maple_devs; if (deser.version() >= Deserializer::V20) { deser >> coin_chute_time; diff --git a/core/hw/naomi/naomi_cart.cpp b/core/hw/naomi/naomi_cart.cpp index a2978a9ea..ce9af41d0 100644 --- a/core/hw/naomi/naomi_cart.cpp +++ b/core/hw/naomi/naomi_cart.cpp @@ -777,7 +777,7 @@ void naomi_cart_serialize(Serializer& ser) void naomi_cart_deserialize(Deserializer& deser) { - if (CurrentCartridge != nullptr && (!settings.platform.isAtomiswave() || deser.version() >= Deserializer::V10_LIBRETRO)) + if (CurrentCartridge != nullptr) CurrentCartridge->Deserialize(deser); touchscreen::deserialize(deser); printer::deserialize(deser); diff --git a/core/hw/pvr/Renderer_if.cpp b/core/hw/pvr/Renderer_if.cpp index aa2b51442..86ce77c7d 100644 --- a/core/hw/pvr/Renderer_if.cpp +++ b/core/hw/pvr/Renderer_if.cpp @@ -536,11 +536,7 @@ void rend_serialize(Serializer& ser) } void rend_deserialize(Deserializer& deser) { - if ((deser.version() >= Deserializer::V12_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO) - || deser.version() >= Deserializer::V12) - deser >> fb_w_cur; - else - fb_w_cur = 1; + deser >> fb_w_cur; if (deser.version() >= Deserializer::V20) { deser >> render_called; diff --git a/core/hw/pvr/pvr.cpp b/core/hw/pvr/pvr.cpp index 79478de0b..22bead33e 100644 --- a/core/hw/pvr/pvr.cpp +++ b/core/hw/pvr/pvr.cpp @@ -104,8 +104,7 @@ void deserialize(Deserializer& deser) deser >> taRenderPass; else taRenderPass = 0; - if (deser.version() >= Deserializer::V11 || (deser.version() >= Deserializer::V10_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO)) - DeserializeTAContext(deser); + DeserializeTAContext(deser); if (!deser.rollback()) vram.deserialize(deser); diff --git a/core/hw/pvr/pvr_mem.cpp b/core/hw/pvr/pvr_mem.cpp index aafc3c62e..a45c88870 100644 --- a/core/hw/pvr/pvr_mem.cpp +++ b/core/hw/pvr/pvr_mem.cpp @@ -190,10 +190,7 @@ void YUV_deserialize(Deserializer& deser) deser >> YUV_y_curr; deser >> YUV_x_size; deser >> YUV_y_size; - if (deser.version() >= Deserializer::V16) - deser >> YUV_index; - else - YUV_index = 0; + deser >> YUV_index; } void YUV_reset() diff --git a/core/hw/pvr/spg.cpp b/core/hw/pvr/spg.cpp index 6cdc5bec0..5fd93bc86 100755 --- a/core/hw/pvr/spg.cpp +++ b/core/hw/pvr/spg.cpp @@ -315,19 +315,11 @@ void spg_Deserialize(Deserializer& deser) if (deser.version() < Deserializer::V30) deser.skip(); // in_vblank deser >> clc_pvr_scanline; - if (deser.version() >= Deserializer::V12) - { - deser >> maple_int_pending; - if (deser.version() >= Deserializer::V14) - { - deser >> pvr_numscanlines; - deser >> prv_cur_scanline; - deser >> Line_Cycles; - deser >> Frame_Cycles; - deser >> lightgun_line; - deser >> lightgun_hpos; - } - } - if (deser.version() < Deserializer::V14) - CalculateSync(); + deser >> maple_int_pending; + deser >> pvr_numscanlines; + deser >> prv_cur_scanline; + deser >> Line_Cycles; + deser >> Frame_Cycles; + deser >> lightgun_line; + deser >> lightgun_hpos; } diff --git a/core/hw/pvr/ta_ctx.cpp b/core/hw/pvr/ta_ctx.cpp index 58c23d0d1..dd64b05a4 100644 --- a/core/hw/pvr/ta_ctx.cpp +++ b/core/hw/pvr/ta_ctx.cpp @@ -249,8 +249,7 @@ static void deserializeContext(Deserializer& deser, TA_context **pctx) tad_context& tad = (*pctx)->tad; deser.deserialize(tad.thd_root, size); tad.thd_data = tad.thd_root + size; - if ((deser.version() >= Deserializer::V12 && deser.version() < Deserializer::V26) - || (deser.version() >= Deserializer::V12_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO)) + if (deser.version() < Deserializer::V26) { u32 render_pass_count; deser >> render_pass_count; diff --git a/core/hw/sh4/modules/mmu.cpp b/core/hw/sh4/modules/mmu.cpp index 54ec72d51..483125fbb 100644 --- a/core/hw/sh4/modules/mmu.cpp +++ b/core/hw/sh4/modules/mmu.cpp @@ -591,8 +591,6 @@ void mmu_deserialize(Deserializer& deser) deser >> UTLB; deser >> ITLB; - if (deser.version() >= Deserializer::V11 - || (deser.version() >= Deserializer::V11_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO)) - deser >> sq_remap; + deser >> sq_remap; deser.skip(64 * 4, Deserializer::V23); // ITLB_LRU_USE } diff --git a/core/hw/sh4/sh4_mmr.cpp b/core/hw/sh4/sh4_mmr.cpp index 5d1579830..5372003a0 100644 --- a/core/hw/sh4/sh4_mmr.cpp +++ b/core/hw/sh4/sh4_mmr.cpp @@ -692,59 +692,24 @@ void serialize(Serializer& ser) sh4_sched_serialize(ser); } -template -static void register_deserialize_libretro(T& regs, Deserializer& deser) -{ - for (auto& reg : regs) - { - deser.skip(); // regs.data[i].flags - deser >> reg; - } -} - void deserialize(Deserializer& deser) { deser >> OnChipRAM; - if (deser.version() <= Deserializer::VLAST_LIBRETRO) - { - register_deserialize_libretro(CCN, deser); - register_deserialize_libretro(UBC, deser); - register_deserialize_libretro(BSC, deser); - register_deserialize_libretro(DMAC, deser); - register_deserialize_libretro(CPG, deser); - register_deserialize_libretro(RTC, deser); - register_deserialize_libretro(INTC, deser); - register_deserialize_libretro(TMU, deser); - register_deserialize_libretro(SCI, deser); - register_deserialize_libretro(SCIF, deser); - } - else - { - deser >> CCN; - deser >> UBC; - deser >> BSC; - deser >> DMAC; - deser >> CPG; - deser >> RTC; - deser >> INTC; - deser >> TMU; - deser >> SCI; - deser >> SCIF; - } + deser >> CCN; + deser >> UBC; + deser >> BSC; + deser >> DMAC; + deser >> CPG; + deser >> RTC; + deser >> INTC; + deser >> TMU; + deser >> SCI; + deser >> SCIF; + SCIFSerialPort::Instance().deserialize(deser); - if (deser.version() >= Deserializer::V9 - // Note (lr): was added in V11 fa49de29 24/12/2020 but ver not updated until V12 (13/4/2021) - || (deser.version() >= Deserializer::V11_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO)) - icache.Deserialize(deser); - else - icache.Reset(true); - if (deser.version() >= Deserializer::V10 - // Note (lr): was added in V11 2eb66879 27/12/2020 but ver not updated until V12 (13/4/2021) - || (deser.version() >= Deserializer::V11_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO)) - ocache.Deserialize(deser); - else - ocache.Reset(true); + icache.Deserialize(deser); + ocache.Deserialize(deser); if (!deser.rollback()) mem_b.deserialize(deser); @@ -778,11 +743,7 @@ void deserialize2(Deserializer& deser) if (deser.version() <= Deserializer::V32) { deser >> SCIF_SCFSR2; - if (deser.version() >= Deserializer::V11 - || (deser.version() >= Deserializer::V11_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO)) - deser >> SCIF_SCSCR2; - else - SCIF_SCSCR2.full = 0; + deser >> SCIF_SCSCR2; deser >> BSC_PDTRA; } diff --git a/core/serialize.cpp b/core/serialize.cpp index 4c6b5735d..1d7548da2 100644 --- a/core/serialize.cpp +++ b/core/serialize.cpp @@ -54,62 +54,8 @@ void dc_serialize(Serializer& ser) DEBUG_LOG(SAVESTATE, "Saved %d bytes", (u32)ser.size()); } -static void dc_deserialize_libretro(Deserializer& deser) -{ - aica::deserialize(deser); - - sb_deserialize(deser); - - nvmem::deserialize(deser); - - gdrom::deserialize(deser); - - mcfg_DeserializeDevices(deser); - - pvr::deserialize(deser); - - sh4::deserialize(deser); - - if (deser.version() >= Deserializer::V13_LIBRETRO) - deser.skip(); // settings.network.EmulateBBA - config::EmulateBBA.override(false); - - ModemDeserialize(deser); - - sh4::deserialize2(deser); - - libGDR_deserialize(deser); - - deser.skip(); // FLASH_SIZE - deser.skip(); // BBSRAM_SIZE - deser.skip(); // BIOS_SIZE - deser.skip(); // RAM_SIZE - deser.skip(); // ARAM_SIZE - deser.skip(); // VRAM_SIZE - deser.skip(); // RAM_MASK - deser.skip(); // ARAM_MASK - deser.skip(); // VRAM_MASK - - naomi_Deserialize(deser); - - deser >> config::Broadcast.get(); - deser >> config::Cable.get(); - deser >> config::Region.get(); - - naomi_cart_deserialize(deser); - gd_hle_state.Deserialize(deser); - - DEBUG_LOG(SAVESTATE, "Loaded %d bytes (libretro compat)", (u32)deser.size()); -} - void dc_deserialize(Deserializer& deser) { - if (deser.version() >= Deserializer::V9_LIBRETRO && deser.version() <= Deserializer::VLAST_LIBRETRO) - { - dc_deserialize_libretro(deser); - sh4_sched_ffts(); - return; - } DEBUG_LOG(SAVESTATE, "Loading state version %d", deser.version()); aica::deserialize(deser); @@ -126,10 +72,7 @@ void dc_deserialize(Deserializer& deser) sh4::deserialize(deser); - if (deser.version() >= Deserializer::V13) - deser >> config::EmulateBBA.get(); - else - config::EmulateBBA.override(false); + deser >> config::EmulateBBA.get(); if (config::EmulateBBA) bba_Deserialize(deser); ModemDeserialize(deser); diff --git a/core/serialize.h b/core/serialize.h index 89ac59ae2..ccd494f9d 100644 --- a/core/serialize.h +++ b/core/serialize.h @@ -26,22 +26,7 @@ class SerializeBase { public: enum Version : int32_t { - V9_LIBRETRO = 8, - V10_LIBRETRO, - V11_LIBRETRO, - V12_LIBRETRO, - V13_LIBRETRO, - VLAST_LIBRETRO = V13_LIBRETRO, - - V8 = 803, - V9, - V10, - V11, - V12, - V13, - V14, - V15, - V16, + V16 = 811, V17, V18, V19, @@ -111,7 +96,7 @@ public: this->limit -= 16; } deserialize(_version); - if (_version < V9_LIBRETRO || (_version > V13_LIBRETRO && _version < V8)) + if (_version < V16) throw Exception("Unsupported version"); if (_version > Current) throw Exception("Version too recent"); @@ -120,11 +105,10 @@ public: { u32 ramSize; deserialize(ramSize); - if (ramSize != settings.platform.ram_size) { + if (ramSize != settings.platform.ram_size) throw Exception("Selected RAM Size doesn't match Save State"); } } - } template void deserialize(T& obj) From e46815d0701ee45ad817007e4536bfb3e644dc27 Mon Sep 17 00:00:00 2001 From: scribam Date: Tue, 16 Apr 2024 15:22:33 +0200 Subject: [PATCH 48/86] ci: update netbsd and openbsd versions --- .github/workflows/bsd.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/bsd.yml b/.github/workflows/bsd.yml index 0509197c5..cd63b20c0 100644 --- a/.github/workflows/bsd.yml +++ b/.github/workflows/bsd.yml @@ -16,10 +16,10 @@ jobs: version: '14.0' pkginstall: sudo pkg install -y alsa-lib ccache cmake evdev-proto git libao libevdev libudev-devd libzip miniupnpc ninja pkgconf pulseaudio sdl2 - operating_system: netbsd - version: '9.3' + version: '10.0' pkginstall: sudo pkgin update && sudo pkgin -y install alsa-lib ccache cmake gcc12 git libao libzip miniupnpc ninja-build pkgconf pulseaudio SDL2 && export PATH=/usr/pkg/gcc12/bin:$PATH - operating_system: openbsd - version: '7.4' + version: '7.5' pkginstall: sudo pkg_add ccache cmake git libao libzip miniupnpc ninja pkgconf pulseaudio sdl2 exclude: - architecture: arm64 @@ -36,7 +36,7 @@ jobs: key: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}-${{ github.sha }} restore-keys: ccache-${{ matrix.operating_system }}-${{ matrix.architecture }}- - - uses: cross-platform-actions/action@v0.23.0 + - uses: cross-platform-actions/action@v0.24.0 with: operating_system: ${{ matrix.operating_system }} architecture: ${{ matrix.architecture }} From 8fdd1dde3def604cdd70a84b19c59e87430f6e4c Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 29 Apr 2024 14:59:47 +0200 Subject: [PATCH 49/86] RetroAchievements support MVP Issue #761 --- .gitmodules | 3 + CMakeLists.txt | 38 + core/achievements/achievements.cpp | 660 ++++++++++++++++++ core/achievements/achievements.h | 41 ++ core/cfg/option.cpp | 7 + core/cfg/option.h | 7 + core/deps/rcheevos | 1 + core/rend/boxart/http_client.cpp | 163 +++-- core/rend/boxart/http_client.h | 1 + core/rend/gui.cpp | 31 +- core/serialize.cpp | 3 + core/serialize.h | 3 +- core/windows/winmain.cpp | 2 + .../com/reicast/emulator/emu/HttpClient.java | 33 + .../flycast/src/main/jni/src/http_client.h | 29 +- shell/apple/common/http_client.mm | 32 + shell/uwp/http_client.cpp | 51 +- tests/src/serialize_test.cpp | 2 +- 18 files changed, 1049 insertions(+), 58 deletions(-) create mode 100644 core/achievements/achievements.cpp create mode 100644 core/achievements/achievements.h create mode 160000 core/deps/rcheevos diff --git a/.gitmodules b/.gitmodules index 77b577576..876c80ebf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -35,3 +35,6 @@ [submodule "core/deps/libadrenotools"] path = core/deps/libadrenotools url = https://github.com/bylaws/libadrenotools +[submodule "core/deps/rcheevos"] + path = core/deps/rcheevos + url = https://github.com/RetroAchievements/rcheevos.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 771eb10c3..046c0522c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1166,6 +1166,44 @@ target_sources(${PROJECT_NAME} PRIVATE core/reios/reios_elf.h) cmrc_add_resources(flycast-resources fonts/biosfont.bin.zip) +target_sources(${PROJECT_NAME} PRIVATE + core/achievements/achievements.cpp + core/achievements/achievements.h) +if(NOT LIBRETRO) + target_sources(${PROJECT_NAME} PRIVATE + core/deps/rcheevos/src/rc_client_raintegration.c + core/deps/rcheevos/src/rc_client.c + core/deps/rcheevos/src/rc_compat.c + core/deps/rcheevos/src/rc_util.c + core/deps/rcheevos/src/rc_version.c + core/deps/rcheevos/src/rapi/rc_api_common.c + core/deps/rcheevos/src/rapi/rc_api_editor.c + core/deps/rcheevos/src/rapi/rc_api_info.c + core/deps/rcheevos/src/rapi/rc_api_runtime.c + core/deps/rcheevos/src/rapi/rc_api_user.c + core/deps/rcheevos/src/rcheevos/alloc.c + core/deps/rcheevos/src/rcheevos/condition.c + core/deps/rcheevos/src/rcheevos/condset.c + core/deps/rcheevos/src/rcheevos/consoleinfo.c + core/deps/rcheevos/src/rcheevos/format.c + core/deps/rcheevos/src/rcheevos/lboard.c + core/deps/rcheevos/src/rcheevos/memref.c + core/deps/rcheevos/src/rcheevos/operand.c + core/deps/rcheevos/src/rcheevos/rc_validate.c + core/deps/rcheevos/src/rcheevos/richpresence.c + core/deps/rcheevos/src/rcheevos/runtime_progress.c + core/deps/rcheevos/src/rcheevos/runtime.c + core/deps/rcheevos/src/rcheevos/trigger.c + core/deps/rcheevos/src/rcheevos/value.c + core/deps/rcheevos/src/rhash/aes.c + core/deps/rcheevos/src/rhash/cdreader.c + core/deps/rcheevos/src/rhash/hash.c + core/deps/rcheevos/src/rhash/md5.c + core/deps/rcheevos/src/rurl/url.c) + target_include_directories(${PROJECT_NAME} PRIVATE core/deps/rcheevos/include) + target_compile_definitions(${PROJECT_NAME} PRIVATE USE_RACHIEVEMENTS RC_DISABLE_LUA) +endif() + target_sources(${PROJECT_NAME} PRIVATE core/wsi/context.h core/wsi/libretro.cpp diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp new file mode 100644 index 000000000..431bd1935 --- /dev/null +++ b/core/achievements/achievements.cpp @@ -0,0 +1,660 @@ +/* + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +// Derived from duckstation: https://github.com/stenzek/duckstation/blob/master/src/core/achievements.cpp +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) +#include "achievements.h" +#include "serialize.h" +#ifdef USE_RACHIEVEMENTS +#include "rend/boxart/http_client.h" // TODO move to oslib? +#include "hw/sh4/sh4_mem.h" +#include "rend/gui.h" +#include "imgread/common.h" +#include "cfg/option.h" +#include "oslib/oslib.h" +#include "emulator.h" +#include "stdclass.h" +#include +#include +#include +#include + +namespace achievements +{ + +class Achievements +{ +public: + Achievements(); + ~Achievements(); + bool init(); + void term(); + void login(const char *username, const char *password); + bool isLoggedOn() const { return loggedOn; } + void serialize(Serializer& ser); + void deserialize(Deserializer& deser); + + static Achievements& Instance(); + +private: + bool createClient(); + std::string getGameHash(); + void loadGame(); + void unloadGame(); + void pauseGame(); + void resumeGame(); + + static void clientLoginWithTokenCallback(int result, const char *error_message, rc_client_t *client, void *userdata); + static void clientLoginWithPasswordCallback(int result, const char *error_message, rc_client_t *client, void *userdata); + static void clientMessageCallback(const char *message, const rc_client_t *client); + static u32 clientReadMemory(u32 address, u8 *buffer, u32 num_bytes, rc_client_t *client); + static void clientServerCall(const rc_api_request_t *request, rc_client_server_callback_t callback, + void *callback_data, rc_client_t *client); + static void clientEventHandler(const rc_client_event_t *event, rc_client_t *client); + static void clientLoadGameCallback(int result, const char *error_message, rc_client_t *client, void *userdata); + void handleResetEvent(const rc_client_event_t *event); + void handleUnlockEvent(const rc_client_event_t *event); + void handleAchievementChallengeIndicatorShowEvent(const rc_client_event_t *event); + void handleAchievementChallengeIndicatorHideEvent(const rc_client_event_t *event); + static void emuEventCallback(Event event, void *arg); + + rc_client_t *rc_client = nullptr; + bool loggedOn = false; + bool active = false; + bool paused = false; + std::string lastError; + cResetEvent resetEvent; + std::thread thread; +}; + +bool init() +{ + return Achievements::Instance().init(); +} + +void term() +{ + Achievements::Instance().term(); +} + +void login(const char *username, const char *password) +{ + Achievements::Instance().login(username, password); +} + +bool isLoggedOn() +{ + return Achievements::Instance().isLoggedOn(); +} + +void serialize(Serializer& ser) +{ + Achievements::Instance().serialize(ser); +} +void deserialize(Deserializer& deser) +{ + Achievements::Instance().deserialize(deser); +} + +Achievements& Achievements::Instance() +{ + static Achievements instance; + return instance; +} +// create the instance at start up +OnLoad _([]() { Achievements::Instance(); }); + +Achievements::Achievements() +{ + EventManager::listen(Event::Start, emuEventCallback, this); + EventManager::listen(Event::Terminate, emuEventCallback, this); + EventManager::listen(Event::Pause, emuEventCallback, this); + EventManager::listen(Event::Resume, emuEventCallback, this); +} + +Achievements::~Achievements() +{ + EventManager::unlisten(Event::Start, emuEventCallback, this); + EventManager::unlisten(Event::Terminate, emuEventCallback, this); + EventManager::unlisten(Event::Pause, emuEventCallback, this); + EventManager::unlisten(Event::Resume, emuEventCallback, this); + term(); +} + +bool Achievements::init() +{ + if (rc_client != nullptr) + return true; + + if (!createClient()) + return false; + + rc_client_set_event_handler(rc_client, clientEventHandler); + + //TODO + rc_client_set_hardcore_enabled(rc_client, 0); + //rc_client_set_encore_mode_enabled(rc_client, 0); + //rc_client_set_unofficial_enabled(rc_client, 0); + //rc_client_set_spectator_mode_enabled(rc_client, 0); + + if (!config::AchievementsUserName.get().empty() && !config::AchievementsToken.get().empty()) + { + INFO_LOG(COMMON, "RA: Attempting login with user '%s'...", config::AchievementsUserName.get().c_str()); + rc_client_begin_login_with_token(rc_client, config::AchievementsUserName.get().c_str(), + config::AchievementsToken.get().c_str(), clientLoginWithTokenCallback, nullptr); + } + + return true; +} + +bool Achievements::createClient() +{ + http::init(); + rc_client = rc_client_create(clientReadMemory, clientServerCall); + if (rc_client == nullptr) + { + WARN_LOG(COMMON, "Can't create RetroAchievements client"); + return false; + } + +#if !defined(NDEBUG) || defined(DEBUGFAST) + rc_client_enable_logging(rc_client, RC_CLIENT_LOG_LEVEL_VERBOSE, clientMessageCallback); +#else + rc_client_enable_logging(rc_client, RC_CLIENT_LOG_LEVEL_WARN, clientMessageCallback); +#endif + + rc_client_set_userdata(rc_client, this); + + return true; +} + +void Achievements::term() +{ + if (rc_client == nullptr) + return; + unloadGame(); + rc_client_destroy(rc_client); + rc_client = nullptr; +} + +static inline void authenticationSuccessMsg() +{ + NOTICE_LOG(COMMON, "RA Login successful: token %s", config::AchievementsToken.get().c_str()); + std::string msg = "User " + config::AchievementsUserName.get() + " authenticated to RetroAchievements"; + gui_display_notification(msg.c_str(), 5000); +} + +void Achievements::clientLoginWithTokenCallback(int result, const char* error_message, rc_client_t* client, + void* userdata) +{ + if (result != RC_OK) + { + WARN_LOG(COMMON, "RA Login failed: %s", error_message); + gui_display_notification(error_message, 10000); + return; + } + + authenticationSuccessMsg(); + Achievements *achievements = (Achievements *)rc_client_get_userdata(client); + achievements->loggedOn = true; +} + +void Achievements::login(const char* username, const char* password) +{ + init(); + rc_client_begin_login_with_password(rc_client, username, password, clientLoginWithPasswordCallback, nullptr); + + if (!loggedOn) + throw FlycastException(lastError); +} + +void Achievements::clientLoginWithPasswordCallback(int result, const char* error_message, rc_client_t* client, + void* userdata) +{ + Achievements *achievements = (Achievements *)rc_client_get_userdata(client); + + if (result != RC_OK) + { + achievements->lastError = rc_error_str(result); + if (error_message != nullptr) + achievements->lastError += ": " + std::string(error_message); + WARN_LOG(COMMON, "RA Login failed: %s", achievements->lastError.c_str()); + return; + } + + const rc_client_user_t* user = rc_client_get_user_info(client); + if (!user || !user->token) + { + WARN_LOG(COMMON, "RA: rc_client_get_user_info() returned NULL"); + return; + } + + // Store token in config + config::AchievementsToken = user->token; + SaveSettings(); + achievements->loggedOn = true; + + authenticationSuccessMsg(); +} + +void Achievements::clientMessageCallback(const char* message, const rc_client_t* client) +{ +#if !defined(NDEBUG) || defined(DEBUGFAST) + DEBUG_LOG(COMMON, "RA: %s", message); +#else + WARN_LOG(COMMON, "RA error: %s", message); +#endif +} + +u32 Achievements::clientReadMemory(u32 address, u8* buffer, u32 num_bytes, rc_client_t* client) +{ + if (address + num_bytes > RAM_SIZE) + return 0; + address += 0x0C000000; + switch (num_bytes) + { + case 1: + *buffer = ReadMem8_nommu(address); + break; + case 2: + *(u16 *)buffer = ReadMem16_nommu(address); + break; + case 4: + *(u32 *)buffer = ReadMem32_nommu(address); + break; + default: + return 0; + } + return num_bytes; +} + +void Achievements::clientServerCall(const rc_api_request_t* request, rc_client_server_callback_t callback, + void* callback_data, rc_client_t* client) +{ + int rc; + std::vector reply; + if (request->post_data != nullptr) + rc = http::post(request->url, request->post_data, reply); + else + rc = http::get(request->url, reply); + rc_api_server_response_t rr; + rr.http_status_code = rc; // TODO RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR if connection fails? + rr.body_length = reply.size(); + rr.body = (const char *)reply.data(); + callback(&rr, callback_data); +} + +static void handleServerError(const rc_client_server_error_t* error) +{ + char buffer[256]; + snprintf(buffer, sizeof(buffer), "%s: %s", error->api, error->error_message); + gui_display_notification(buffer, 5000); +} + +void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_t* client) +{ + Achievements *achievements = (Achievements *)rc_client_get_userdata(client); + switch (event->type) + { + case RC_CLIENT_EVENT_RESET: + achievements->handleResetEvent(event); + break; + + case RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED: + achievements->handleUnlockEvent(event); + break; + + case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW: + achievements->handleAchievementChallengeIndicatorShowEvent(event); + break; + + case RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE: + achievements->handleAchievementChallengeIndicatorHideEvent(event); + break; + + case RC_CLIENT_EVENT_SERVER_ERROR: + handleServerError(event->server_error); + break; + +/* + TODO + case RC_CLIENT_EVENT_GAME_COMPLETED: + case RC_CLIENT_EVENT_LEADERBOARD_STARTED: + case RC_CLIENT_EVENT_LEADERBOARD_FAILED: + case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED: + case RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD: + case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW: + case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE: + case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE: + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW: + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE: + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE: + case RC_CLIENT_EVENT_SERVER_ERROR: + case RC_CLIENT_EVENT_DISCONNECTED: + case RC_CLIENT_EVENT_RECONNECTED: +*/ + default: + WARN_LOG(COMMON, "RA: Unhandled event: %u", event->type); + break; + } +} + +void Achievements::handleResetEvent(const rc_client_event_t *event) +{ + INFO_LOG(COMMON, "RA: Resetting runtime due to reset event"); + rc_client_reset(rc_client); +} + +void Achievements::handleUnlockEvent(const rc_client_event_t *event) +{ + const rc_client_achievement_t* cheevo = event->achievement; + assert(cheevo != nullptr); + + INFO_LOG(COMMON, "RA: Achievement %s (%u) for game %s unlocked", cheevo->title, cheevo->id, settings.content.title.c_str()); + + std::string msg = "Achievement " + std::string(cheevo->title) + " unlocked!"; + gui_display_notification(msg.c_str(), 10000); +} + +void Achievements::handleAchievementChallengeIndicatorShowEvent(const rc_client_event_t *event) +{ + // TODO there might be more than one. Need to display an icon. + std::string msg = "Challenge: " + std::string(event->achievement->title); + gui_display_notification(msg.c_str(), 10000); +} + +void Achievements::handleAchievementChallengeIndicatorHideEvent(const rc_client_event_t *event) +{ + gui_display_notification("", 1); +} + +static bool add150; + +static void *cdreader_open_track(const char* path, u32 track) +{ + DEBUG_LOG(COMMON, "RA: cdreader_open_track %s track %d", path, track); + if (track == RC_HASH_CDTRACK_FIRST_DATA) + { + u32 toc[102]; + libGDR_GetToc(toc, SingleDensity); + for (int i = 0; i < 99; i++) + if (toc[i] != 0xffffffff) + { + if (((toc[i] >> 4) & 0xf) & 4) + return reinterpret_cast(i + 1); + } + if (libGDR_GetDiscType() == GdRom) + { + libGDR_GetToc(toc, DoubleDensity); + for (int i = 0; i < 99; i++) + if (toc[i] != 0xffffffff) + { + if (((toc[i] >> 4) & 0xf) & 4) + return reinterpret_cast(i + 1); + } + } + } + u32 start, end; + if (!libGDR_GetTrack(track, start, end)) + return nullptr; + else + return reinterpret_cast(track); +} + +static size_t cdreader_read_sector(void* track_handle, u32 sector, void* buffer, size_t requested_bytes) +{ + //DEBUG_LOG(COMMON, "RA: cdreader_read_sector track %zd sec %d num %zd", reinterpret_cast(track_handle), sector, requested_bytes); + if (requested_bytes == 2048) + add150 = true; + if (add150) // FIXME how to get rid of this? + sector += 150; + u32 count = requested_bytes; + u32 secNum = count / 2048; + if (secNum > 0) + { + libGDR_ReadSector((u8 *)buffer, sector, secNum, 2048); + buffer = (u8 *)buffer + secNum * 2048; + count -= secNum * 2048; + } + if (count > 0) + { + u8 locbuf[2048]; + libGDR_ReadSector(locbuf, sector + secNum, 1, 2048); + memcpy(buffer, locbuf, count); + } + return requested_bytes; +} + +static void cdreader_close_track(void* track_handle) +{ +} + +static u32 cdreader_first_track_sector(void* track_handle) +{ + u32 trackNum = reinterpret_cast(track_handle); + u32 start, end; + if (!libGDR_GetTrack(trackNum, start, end)) + return 0; + DEBUG_LOG(COMMON, "RA: cdreader_first_track_sector track %d -> %d", trackNum, start); + return start; +} + +std::string Achievements::getGameHash() +{ + if (settings.platform.isConsole()) + { + add150 = false; + rc_hash_cdreader hooks = { + cdreader_open_track, + cdreader_read_sector, + cdreader_close_track, + cdreader_first_track_sector + }; + rc_hash_init_custom_cdreader(&hooks); + rc_hash_init_error_message_callback([](const char *msg) { + WARN_LOG(COMMON, "cdreader: %s", msg); + }); +#ifndef NDEBUG + rc_hash_init_verbose_message_callback([](const char *msg) { + DEBUG_LOG(COMMON, "cdreader: %s", msg); + }); +#endif + } + char hash[33] {}; + rc_hash_generate_from_file(hash, settings.platform.isConsole() ? RC_CONSOLE_DREAMCAST : RC_CONSOLE_ARCADE, + settings.content.fileName.c_str()); // fileName is only used for arcade + + return hash; +} + +void Achievements::pauseGame() +{ + paused = true; + if (active) + resetEvent.Reset(); +} + +void Achievements::resumeGame() +{ + paused = false; + if (config::EnableAchievements) + loadGame(); + else + term(); +} + +void Achievements::emuEventCallback(Event event, void *arg) +{ + Achievements *instance = ((Achievements *)arg); + switch (event) + { + case Event::Start: + case Event::Resume: + instance->resumeGame(); + break; + case Event::Terminate: + instance->unloadGame(); + break; + case Event::VBlank: + instance->resetEvent.Set(); + break; + case Event::Pause: + instance->pauseGame(); + break; + default: + break; + } +} + +void Achievements::loadGame() +{ + if (active) + // already loaded + return; + if (!init() || !isLoggedOn()) + return; + std::string gameHash = getGameHash(); + if (!gameHash.empty()) + rc_client_begin_load_game(rc_client, gameHash.c_str(), clientLoadGameCallback, nullptr); + if (!active) + return; + thread = std::thread([this] { + ThreadName _("Flycast-RA"); + while (active) + { + if (!resetEvent.Wait(1000)) { + if (paused) { + DEBUG_LOG(COMMON, "RA: rc_client_idle"); + rc_client_idle(rc_client); + } + else { + INFO_LOG(COMMON, "RA: timeout on event and not paused!"); + } + } + else if (active) + rc_client_do_frame(rc_client); + } + }); + EventManager::listen(Event::VBlank, emuEventCallback, this); +} + +void Achievements::unloadGame() +{ + if (!active) + return; + active = false; + resetEvent.Set(); + thread.join(); + EventManager::unlisten(Event::VBlank, emuEventCallback, this); + rc_client_unload_game(rc_client); +} + +void Achievements::clientLoadGameCallback(int result, const char* error_message, rc_client_t* client, void* userdata) +{ + Achievements *achiev = (Achievements *)rc_client_get_userdata(client); + if (result == RC_NO_GAME_LOADED) + { + // Unknown game. + INFO_LOG(COMMON, "RA: Unknown game, disabling achievements."); + return; + } + if (result == RC_LOGIN_REQUIRED) + { + // We would've asked to re-authenticate, so leave HC on for now. + // Once we've done so, we'll reload the game. + return; + } + if (result != RC_OK) + { + WARN_LOG(COMMON, "RA Loading game failed: %s", error_message); + return; + } + const rc_client_game_t* info = rc_client_get_game_info(client); + if (info == nullptr) + { + WARN_LOG(COMMON, "RA: rc_client_get_game_info() returned NULL"); + return; + } + // TODO? rc_client_game_get_image_url(info, buf, std::size(buf)); + achiev->active = true; + NOTICE_LOG(COMMON, "RA: game %d loaded: %s, achievements %d leaderboards %d rich presence %d", info->id, info->title, + rc_client_has_achievements(client), rc_client_has_leaderboards(client), rc_client_has_rich_presence(client)); +} + +void Achievements::serialize(Serializer& ser) +{ + u32 size = (u32)rc_client_progress_size(rc_client); + if (size > 0) + { + u8 *buffer = new u8[size]; + if (rc_client_serialize_progress(rc_client, buffer) != RC_OK) + size = 0; + ser << size; + ser.serialize(buffer, size); + delete[] buffer; + } + else { + ser << size; + } +} +void Achievements::deserialize(Deserializer& deser) +{ + if (deser.version() < Deserializer::V50) { + rc_client_deserialize_progress(rc_client, nullptr); + } + else { + u32 size; + deser >> size; + if (size > 0) + { + u8 *buffer = new u8[size]; + deser.deserialize(buffer, size); + rc_client_deserialize_progress(rc_client, buffer); + delete[] buffer; + } + else { + rc_client_deserialize_progress(rc_client, nullptr); + } + } +} + +} // namespace achievements + +#else // !USE_RACHIEVEMENTS + +namespace achievements +{ + +// Maintain savestate compatiblity with RA-enabled builds +void serialize(Serializer& ser) +{ + u32 size = 0; + ser << size; +} + +void deserialize(Deserializer& deser) +{ + if (deser.version() >= Deserializer::V50) + { + u32 size; + deser >> size; + deser.skip(size); + } +} + +} +#endif diff --git a/core/achievements/achievements.h b/core/achievements/achievements.h new file mode 100644 index 000000000..e029ff1b9 --- /dev/null +++ b/core/achievements/achievements.h @@ -0,0 +1,41 @@ +/* + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#pragma once +#include "types.h" + +namespace achievements +{ +#ifdef USE_RACHIEVEMENTS + +bool init(); +void term(); +void login(const char *username, const char *password); +bool isLoggedOn(); + +#else + +static inline bool init() { return false; } +static inline void term() {} +static inline void login(const char *username, const char *password) {} +static inline bool isLoggedOn() { return false; } + +#endif + +void serialize(Serializer& ser); +void deserialize(Deserializer& deser); + +} diff --git a/core/cfg/option.cpp b/core/cfg/option.cpp index e8fa133c8..b3bd2e5ad 100644 --- a/core/cfg/option.cpp +++ b/core/cfg/option.cpp @@ -197,4 +197,11 @@ Option UseRawInput("RawInput", false, "input"); Option LuaFileName("LuaFileName", "flycast.lua"); #endif +// RetroAchievements + +Option EnableAchievements("Enabled", false, "achievements"); +Option AchievementsHardcoreMode("HardcoreMode", false, "achievements"); +OptionString AchievementsUserName("UserName", "", "achievements"); +OptionString AchievementsToken("Token", "", "achievements"); + } // namespace config diff --git a/core/cfg/option.h b/core/cfg/option.h index 0890b6260..c5ecd239b 100644 --- a/core/cfg/option.h +++ b/core/cfg/option.h @@ -547,4 +547,11 @@ constexpr bool UseRawInput = false; extern Option LuaFileName; #endif +// RetroAchievements + +extern Option EnableAchievements; +extern Option AchievementsHardcoreMode; +extern OptionString AchievementsUserName; +extern OptionString AchievementsToken; + } // namespace config diff --git a/core/deps/rcheevos b/core/deps/rcheevos new file mode 160000 index 000000000..e88f74992 --- /dev/null +++ b/core/deps/rcheevos @@ -0,0 +1 @@ +Subproject commit e88f74992527b9ade48ae1591378ec2cf363bef9 diff --git a/core/rend/boxart/http_client.cpp b/core/rend/boxart/http_client.cpp index d68db142f..52b707743 100644 --- a/core/rend/boxart/http_client.cpp +++ b/core/rend/boxart/http_client.cpp @@ -68,6 +68,84 @@ int get(const std::string& url, std::vector& content, std::string& contentTy return 200; } +static int post(const std::string& url, const char *headers, const u8 *payload, u32 payloadSize, std::vector& reply) +{ + char scheme[16], host[256], path[256]; + URL_COMPONENTS components{}; + components.dwStructSize = sizeof(components); + components.lpszScheme = scheme; + components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]); + components.lpszHostName = host; + components.dwHostNameLength = sizeof(host) / sizeof(host[0]); + components.lpszUrlPath = path; + components.dwUrlPathLength = sizeof(path) / sizeof(path[0]); + + if (!InternetCrackUrlA(url.c_str(), url.length(), 0, &components)) + return 500; + + bool https = !strcmp(scheme, "https"); + + int rc = 500; + HINTERNET ic = InternetConnect(hInet, host, components.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); + if (ic == NULL) + return rc; + + HINTERNET hreq = HttpOpenRequest(ic, "POST", path, NULL, NULL, NULL, https ? INTERNET_FLAG_SECURE : 0, 0); + if (hreq == NULL) { + InternetCloseHandle(ic); + return rc; + } + if (payloadSize > 0) + { + char clen[128]; + if (headers == nullptr) + snprintf(clen, sizeof(clen), "Content-Length: %d\r\nContent-Type: application/x-www-form-urlencoded\r\n", payloadSize); + else + snprintf(clen, sizeof(clen), "Content-Length: %d\r\n", payloadSize); + HttpAddRequestHeaders(hreq, clen, -1L, HTTP_ADDREQ_FLAG_ADD_IF_NEW); + } + if (!HttpSendRequest(hreq, headers, -1, (void *)payload, payloadSize)) + WARN_LOG(NETWORK, "HttpSendRequest Error %d", GetLastError()); + else + { + DWORD status; + DWORD size = sizeof(status); + DWORD index = 0; + if (!HttpQueryInfo(hreq, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &size, &index)) + WARN_LOG(NETWORK, "HttpQueryInfo Error %d", GetLastError()); + else + { + rc = status; + reply.clear(); + u8 buffer[4096]; + DWORD bytesRead = sizeof(buffer); + while (true) + { + if (!InternetReadFile(hreq, buffer, sizeof(buffer), &bytesRead)) + { + WARN_LOG(NETWORK, "InternetReadFile failed: %lx", GetLastError()); + InternetCloseHandle(hreq); + rc = 500; + break; + } + if (bytesRead == 0) + break; + reply.insert(reply.end(), buffer, buffer + bytesRead); + } + } + } + + InternetCloseHandle(hreq); + InternetCloseHandle(ic); + + return rc; +} + +int post(const std::string& url, const char *payload, std::vector& reply) +{ + return post(url, nullptr, (const u8 *)payload, strlen(payload), reply); +} + int post(const std::string& url, const std::vector& fields) { static const std::string boundary("----flycast-boundary-8304529454"); @@ -122,49 +200,9 @@ int post(const std::string& url, const std::vector& fields) } content += "--" + boundary + "--\r\n"; - char scheme[16], host[256], path[256]; - URL_COMPONENTS components{}; - components.dwStructSize = sizeof(components); - components.lpszScheme = scheme; - components.dwSchemeLength = sizeof(scheme) / sizeof(scheme[0]); - components.lpszHostName = host; - components.dwHostNameLength = sizeof(host) / sizeof(host[0]); - components.lpszUrlPath = path; - components.dwUrlPathLength = sizeof(path) / sizeof(path[0]); - - if (!InternetCrackUrlA(url.c_str(), url.length(), 0, &components)) - return 500; - - bool https = !strcmp(scheme, "https"); - - int rc = 500; - HINTERNET ic = InternetConnect(hInet, host, components.nPort, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); - if (ic == NULL) - return rc; - - HINTERNET hreq = HttpOpenRequest(ic, "POST", path, NULL, NULL, NULL, https ? INTERNET_FLAG_SECURE : 0, 0); - if (hreq == NULL) { - InternetCloseHandle(ic); - return rc; - } + std::vector reply; std::string header("Content-Type: multipart/form-data; boundary=" + boundary); - if (!HttpSendRequest(hreq, header.c_str(), -1, &content[0], content.length())) - WARN_LOG(NETWORK, "HttpSendRequest Error %d", GetLastError()); - else - { - DWORD status; - DWORD size = sizeof(status); - DWORD index = 0; - if (!HttpQueryInfo(hreq, HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, &status, &size, &index)) - WARN_LOG(NETWORK, "HttpQueryInfo Error %d", GetLastError()); - else - rc = status; - } - - InternetCloseHandle(hreq); - InternetCloseHandle(ic); - - return rc; + return post(url, header.c_str(), (const u8 *)&content[0], content.length(), reply); } void term() @@ -195,7 +233,7 @@ static size_t receiveData(void *buffer, size_t size, size_t nmemb, std::vector& content, std::string& contentType) +static CURL *makeCurlEasy(const std::string& url) { CURL *curl = curl_easy_init(); curl_easy_setopt(curl, CURLOPT_USERAGENT, "Flycast/1.0"); @@ -206,6 +244,13 @@ int get(const std::string& url, std::vector& content, std::string& contentTy curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + return curl; +} + +int get(const std::string& url, std::vector& content, std::string& contentType) +{ + CURL *curl = makeCurlEasy(url); + std::vector recvBuffer; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &recvBuffer); @@ -227,17 +272,34 @@ int get(const std::string& url, std::vector& content, std::string& contentTy return (int)httpCode; } -int post(const std::string& url, const std::vector& fields) +int post(const std::string& url, const char *payload, std::vector& reply) { - CURL *curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_USERAGENT, "Flycast/1.0"); - curl_easy_setopt(curl, CURLOPT_AUTOREFERER, 1); - curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1); + CURL *curl = makeCurlEasy(url); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); - curl_easy_setopt(curl, CURLOPT_COOKIEFILE, ""); + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); - curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + std::vector recvBuffer; + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &recvBuffer); + CURLcode res = curl_easy_perform(curl); + + long httpCode = 500; + if (res == CURLE_OK) + { + curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); + reply = recvBuffer; + } + curl_easy_cleanup(curl); + + return (int)httpCode; +} + +int post(const std::string& url, const std::vector& fields) +{ + CURL *curl = makeCurlEasy(url); + curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_mime *mime = curl_mime_init(curl); for (const auto& field : fields) @@ -263,7 +325,6 @@ int post(const std::string& url, const std::vector& fields) curl_easy_cleanup(curl); return (int)httpCode; - } void term() diff --git a/core/rend/boxart/http_client.h b/core/rend/boxart/http_client.h index 846abc126..f4493670d 100644 --- a/core/rend/boxart/http_client.h +++ b/core/rend/boxart/http_client.h @@ -50,6 +50,7 @@ struct PostField }; int post(const std::string& url, const std::vector& fields); +int post(const std::string& url, const char *payload, std::vector& reply); static inline bool success(int status) { return status >= 200 && status < 300; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index db6398486..b99f518d6 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -45,6 +45,7 @@ #include "profiler/fc_profiler.h" #include "hw/naomi/card_reader.h" #include "oslib/resources.h" +#include "achievements/achievements.h" #if defined(USE_SDL) #include "sdl/sdl.h" #endif @@ -1739,7 +1740,35 @@ static void gui_display_settings() #if USE_DISCORD OptionCheckbox("Discord Presence", config::DiscordPresence, "Show which game you are playing on Discord"); #endif - + OptionCheckbox("Enable RetroAchievements", config::EnableAchievements, "Track your game achievements using RetroAchievements.org"); + { + DisabledScope _(!config::EnableAchievements); + ImGui::Indent(); + char username[256]; + strcpy(username, config::AchievementsUserName.get().c_str()); + ImGui::InputText("Username", username, sizeof(username), ImGuiInputTextFlags_None, nullptr, nullptr); + config::AchievementsUserName = username; + if (config::EnableAchievements) + { + achievements::init(); + if (achievements::isLoggedOn()) + ImGui::Text("Authentication successful"); + else + { + static char password[256]; + ImGui::InputText("Password", password, sizeof(password), ImGuiInputTextFlags_Password, nullptr, nullptr); + if (ImGui::Button("Login", ScaledVec2(100, 0))) + { + try { + achievements::login(config::AchievementsUserName.get().c_str(), password); + } catch (const FlycastException& e) { + gui_error(e.what()); + } + } + } + } + ImGui::Unindent(); + } ImGui::PopStyleVar(); ImGui::EndTabItem(); } diff --git a/core/serialize.cpp b/core/serialize.cpp index 1d7548da2..9094e164b 100644 --- a/core/serialize.cpp +++ b/core/serialize.cpp @@ -16,6 +16,7 @@ #include "hw/bba/bba.h" #include "cfg/option.h" #include "imgread/common.h" +#include "achievements/achievements.h" void dc_serialize(Serializer& ser) { @@ -50,6 +51,7 @@ void dc_serialize(Serializer& ser) naomi_cart_serialize(ser); gd_hle_state.Serialize(ser); + achievements::serialize(ser); DEBUG_LOG(SAVESTATE, "Saved %d bytes", (u32)ser.size()); } @@ -92,6 +94,7 @@ void dc_deserialize(Deserializer& deser) naomi_cart_deserialize(deser); gd_hle_state.Deserialize(deser); + achievements::deserialize(deser); sh4_sched_ffts(); DEBUG_LOG(SAVESTATE, "Loaded %d bytes", (u32)deser.size()); diff --git a/core/serialize.h b/core/serialize.h index ccd494f9d..2c9ebb813 100644 --- a/core/serialize.h +++ b/core/serialize.h @@ -60,7 +60,8 @@ public: V47, V48, V49, - Current = V49, + V50, + Current = V50, Next = Current + 1, }; diff --git a/core/windows/winmain.cpp b/core/windows/winmain.cpp index 302e07dfd..9146916f5 100644 --- a/core/windows/winmain.cpp +++ b/core/windows/winmain.cpp @@ -469,6 +469,7 @@ void os_RunInstance(int argc, const char *argv[]) void os_SetThreadName(const char *name) { +#ifndef TARGET_UWP nowide::wstackstring wname; if (wname.convert(name)) { @@ -483,6 +484,7 @@ void os_SetThreadName(const char *name) if (SetThreadDescription != nullptr) SetThreadDescription(GetCurrentThread(), wname.get()); } +#endif } #ifdef VIDEO_ROUTING diff --git a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/emu/HttpClient.java b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/emu/HttpClient.java index 17deee0e0..297507206 100644 --- a/shell/android-studio/flycast/src/main/java/com/reicast/emulator/emu/HttpClient.java +++ b/shell/android-studio/flycast/src/main/java/com/reicast/emulator/emu/HttpClient.java @@ -27,6 +27,7 @@ import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; import org.apache.hc.client5.http.impl.classic.HttpClients; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpEntity; +import org.apache.hc.core5.http.io.entity.StringEntity; import java.io.ByteArrayOutputStream; import java.io.File; @@ -77,6 +78,38 @@ public class HttpClient { return 500; } + public int post(String urlString, String payload, byte[][] reply) { + try { + if (httpClient == null) + httpClient = HttpClients.createDefault(); + HttpPost httpPost = new HttpPost(urlString); + httpPost.setEntity(new StringEntity(payload, ContentType.APPLICATION_FORM_URLENCODED)); + CloseableHttpResponse response = httpClient.execute(httpPost); + InputStream is = response.getEntity().getContent(); + + byte[] buffer = new byte[1024]; + int length; + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + while ((length = is.read(buffer)) > 0) { + baos.write(buffer, 0, length); + } + is.close(); + baos.close(); + reply[0] = baos.toByteArray(); + + return response.getCode(); + } catch (MalformedURLException e) { + Log.e("flycast", "Malformed URL", e); + } catch (IOException e) { + Log.e("flycast", "I/O error", e); + } catch (SecurityException e) { + Log.e("flycast", "Security error", e); + } catch (Throwable t) { + Log.e("flycast", "Unknown error", t); + } + return 500; + } public int post(String urlString, String[] fieldNames, String[] fieldValues, String[] contentTypes) { try { diff --git a/shell/android-studio/flycast/src/main/jni/src/http_client.h b/shell/android-studio/flycast/src/main/jni/src/http_client.h index 83072c95e..628fdb902 100644 --- a/shell/android-studio/flycast/src/main/jni/src/http_client.h +++ b/shell/android-studio/flycast/src/main/jni/src/http_client.h @@ -25,6 +25,7 @@ namespace http { static jobject HttpClient; static jmethodID openUrlMid; static jmethodID postMid; + static jmethodID postRawMid; void init() { } @@ -35,8 +36,10 @@ namespace http { jni::ObjectArray contentOut(1); jni::ObjectArray contentTypeOut(1); - int httpStatus = jni::env()->CallIntMethod(HttpClient, openUrlMid, (jstring)jurl, (jobjectArray)contentOut, - (jobjectArray)contentTypeOut); + int httpStatus = jni::env()->CallIntMethod(HttpClient, openUrlMid, + static_cast(jurl), + static_cast(contentOut), + static_cast(contentTypeOut)); content = contentOut[0]; contentType = contentTypeOut[0]; @@ -44,6 +47,21 @@ namespace http { return httpStatus; } + int post(const std::string &url, const char *payload, std::vector& reply) + { + jni::String jurl(url); + jni::String jpayload(payload); + jni::ObjectArray replyOut(1); + + int httpStatus = jni::env()->CallIntMethod(HttpClient, postRawMid, + static_cast(jurl), + static_cast(jpayload), + static_cast(replyOut)); + reply = replyOut[0]; + + return httpStatus; + } + int post(const std::string &url, const std::vector& fields) { jni::String jurl(url); @@ -58,7 +76,11 @@ namespace http { contentTypes.setAt(i, fields[i].contentType); } - int httpStatus = jni::env()->CallIntMethod(HttpClient, postMid, (jstring)jurl, (jobjectArray)names, (jobjectArray)values, (jobjectArray)contentTypes); + int httpStatus = jni::env()->CallIntMethod(HttpClient, postMid, + static_cast(jurl), + static_cast(names), + static_cast(values), + static_cast(contentTypes)); return httpStatus; } @@ -73,4 +95,5 @@ extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_HttpClient_nativ http::HttpClient = env->NewGlobalRef(obj); http::openUrlMid = env->GetMethodID(env->GetObjectClass(obj), "openUrl", "(Ljava/lang/String;[[B[Ljava/lang/String;)I"); http::postMid = env->GetMethodID(env->GetObjectClass(obj), "post", "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)I"); + http::postRawMid = env->GetMethodID(env->GetObjectClass(obj), "post", "(Ljava/lang/String;Ljava/lang/String;[[B)I"); } diff --git a/shell/apple/common/http_client.mm b/shell/apple/common/http_client.mm index e1f411533..ec76832b3 100644 --- a/shell/apple/common/http_client.mm +++ b/shell/apple/common/http_client.mm @@ -46,6 +46,38 @@ int get(const std::string& url, std::vector& content, std::string& contentTy return [httpResponse statusCode]; } +int post(const std::string& url, const char *payload, std::vector& reply) +{ + NSString *nsurl = [NSString stringWithCString:url.c_str() + encoding:[NSString defaultCStringEncoding]]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:nsurl]]; + [request setHTTPMethod:@"POST"]; + [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; + [request setHTTPShouldHandleCookies:NO]; + + size_t payloadSize = strlen(payload); + [request setHTTPBody:[NSData dataWithBytes:payload length:payloadSize]]; + NSString *postLength = [NSString stringWithFormat:@"%ld", (unsigned long)payloadSize]; + [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; + NSString *contentType = @"application/x-www-form-urlencoded"; + [request setValue:contentType forHTTPHeaderField:@"Content-Type"]; + + NSURLResponse *response = nil; + NSError *error = nil; + NSData *data = [NSURLConnection sendSynchronousRequest:request + returningResponse:&response + error:&error]; + if (error != nil) + return 500; + + NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response; + + reply.clear(); + reply.insert(reply.begin(), (const u8 *)[data bytes], (const u8 *)[data bytes] + [data length]); + + return [httpResponse statusCode]; +} + int post(const std::string& url, const std::vector& fields) { NSString *nsurl = [NSString stringWithCString:url.c_str() diff --git a/shell/uwp/http_client.cpp b/shell/uwp/http_client.cpp index af787c925..27f13e7ab 100644 --- a/shell/uwp/http_client.cpp +++ b/shell/uwp/http_client.cpp @@ -89,7 +89,56 @@ int get(const std::string& url, std::vector& content, std::string& contentTy } } -int post(const std::string& url, const std::vector& fields) { +int post(const std::string& url, const char *payload, std::vector& reply) +{ + nowide::wstackstring wurl; + if (!wurl.convert(url.c_str())) + return 500; + nowide::wstackstring wpayload; + if (!wpayload.convert(payload)) + return 500; + try + { + Uri^ uri = ref new Uri(ref new String(wurl.get())); + HttpStringContent^ content = ref new HttpStringContent(ref new String(wpayload.get())); + content->Headers->ContentLength = strlen(payload); + content->Headers->ContentType = ref new HttpMediaTypeHeaderValue("application/x-www-form-urlencoded"); + + IAsyncOperationWithProgress^ op = httpClient->PostAsync(uri, content); + cResetEvent asyncEvent; + op->Completed = ref new AsyncOperationWithProgressCompletedHandler( + [&asyncEvent](IAsyncOperationWithProgress^, AsyncStatus) { + asyncEvent.Set(); + }); + asyncEvent.Wait(); + HttpResponseMessage^ resp = op->GetResults(); + + if (resp->IsSuccessStatusCode) + { + IHttpContent^ httpContent = resp->Content; + IAsyncOperationWithProgress^ readOp = httpContent->ReadAsBufferAsync(); + asyncEvent.Reset(); + readOp->Completed = ref new AsyncOperationWithProgressCompletedHandler( + [&asyncEvent](IAsyncOperationWithProgress^, AsyncStatus) { + asyncEvent.Set(); + }); + asyncEvent.Wait(); + IBuffer^ buffer = readOp->GetResults(); + + Array^ array = ref new Array(buffer->Length); + DataReader::FromBuffer(buffer)->ReadBytes(array); + reply = std::vector(array->begin(), array->end()); + } + return (int)resp->StatusCode; + } + catch (Exception^ e) + { + WARN_LOG(COMMON, "http::post error %.*S", e->Message->Length(), e->Message->Data()); + return 500; + } +} + +int post(const std::string & url, const std::vector&fields) { // not implemented return 500; } diff --git a/tests/src/serialize_test.cpp b/tests/src/serialize_test.cpp index 1ef30fc84..168699ce6 100644 --- a/tests/src/serialize_test.cpp +++ b/tests/src/serialize_test.cpp @@ -32,7 +32,7 @@ TEST_F(SerializeTest, SizeTest) std::vector data(30000000); Serializer ser(data.data(), data.size()); dc_serialize(ser); - ASSERT_EQ(28191439u, ser.size()); + ASSERT_EQ(28191443u, ser.size()); } From 4666ea2fdba1a0c225dc99b7cb693962ad494749 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 29 Apr 2024 15:31:21 +0200 Subject: [PATCH 50/86] macos: bump minimum macOS version to 10.11 to avoid build error --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9af465ebe..8ac67a9be 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ if(APPLE) set(CMAKE_OSX_DEPLOYMENT_TARGET "12.0" CACHE STRING "Minimum tvOS deployment version") set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "") else() - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.9" CACHE STRING "Minimum macOS deployment version") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.11" CACHE STRING "Minimum macOS deployment version") set(CMAKE_OSX_ARCHITECTURES "x86_64;arm64" CACHE STRING "") endif() endif() From c96e828c638f0b7e3a4073283567b130059288cc Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 29 Apr 2024 16:17:50 +0200 Subject: [PATCH 51/86] move http_client to oslib --- CMakeLists.txt | 4 ++-- core/achievements/achievements.cpp | 2 +- core/{rend/boxart => oslib}/http_client.cpp | 0 core/{rend/boxart => oslib}/http_client.h | 0 core/oslib/oslib.cpp | 2 +- core/rend/boxart/gamesdb.cpp | 2 +- core/rend/boxart/scraper.cpp | 4 ++-- shell/android-studio/flycast/src/main/jni/src/http_client.h | 2 +- shell/apple/common/http_client.mm | 2 +- shell/uwp/http_client.cpp | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) rename core/{rend/boxart => oslib}/http_client.cpp (100%) rename core/{rend/boxart => oslib}/http_client.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8ac67a9be..ff6334a76 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1129,6 +1129,8 @@ if(NOT LIBRETRO) core/audio/audiobackend_pulseaudio.cpp core/audio/audiobackend_sdl2.cpp core/audio/audiostream.cpp + core/oslib/http_client.cpp + core/oslib/http_client.h core/oslib/oslib.cpp) endif() @@ -1302,8 +1304,6 @@ if(NOT LIBRETRO) core/rend/boxart/boxart.h core/rend/boxart/gamesdb.cpp core/rend/boxart/gamesdb.h - core/rend/boxart/http_client.cpp - core/rend/boxart/http_client.h core/rend/boxart/scraper.cpp core/rend/boxart/scraper.h) endif() diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp index 431bd1935..028c93ef1 100644 --- a/core/achievements/achievements.cpp +++ b/core/achievements/achievements.cpp @@ -20,7 +20,7 @@ #include "achievements.h" #include "serialize.h" #ifdef USE_RACHIEVEMENTS -#include "rend/boxart/http_client.h" // TODO move to oslib? +#include "oslib/http_client.h" #include "hw/sh4/sh4_mem.h" #include "rend/gui.h" #include "imgread/common.h" diff --git a/core/rend/boxart/http_client.cpp b/core/oslib/http_client.cpp similarity index 100% rename from core/rend/boxart/http_client.cpp rename to core/oslib/http_client.cpp diff --git a/core/rend/boxart/http_client.h b/core/oslib/http_client.h similarity index 100% rename from core/rend/boxart/http_client.h rename to core/oslib/http_client.h diff --git a/core/oslib/oslib.cpp b/core/oslib/oslib.cpp index 38e9d0d30..ad3b0561b 100644 --- a/core/oslib/oslib.cpp +++ b/core/oslib/oslib.cpp @@ -219,7 +219,7 @@ void os_UpdateInputState() #ifdef USE_BREAKPAD -#include "rend/boxart/http_client.h" +#include "http_client.h" #include "version.h" #include "log/InMemoryListener.h" #include "wsi/context.h" diff --git a/core/rend/boxart/gamesdb.cpp b/core/rend/boxart/gamesdb.cpp index f03fc03c6..8a092d7d5 100644 --- a/core/rend/boxart/gamesdb.cpp +++ b/core/rend/boxart/gamesdb.cpp @@ -17,7 +17,7 @@ along with Flycast. If not, see . */ #include "gamesdb.h" -#include "http_client.h" +#include "oslib/http_client.h" #include "stdclass.h" #include "emulator.h" diff --git a/core/rend/boxart/scraper.cpp b/core/rend/boxart/scraper.cpp index 0c905ba71..91d97513a 100644 --- a/core/rend/boxart/scraper.cpp +++ b/core/rend/boxart/scraper.cpp @@ -17,7 +17,7 @@ along with Flycast. If not, see . */ #include "scraper.h" -#include "http_client.h" +#include "oslib/http_client.h" #include "stdclass.h" #include "emulator.h" #include "imgread/common.h" @@ -148,7 +148,7 @@ void OfflineScraper::scrape(GameBoxart& item) FILE *f = nowide::fopen((const char *)context, "wb"); if (f == nullptr) { - WARN_LOG(COMMON, "can't create local file %s: error %d", context, errno); + WARN_LOG(COMMON, "can't create local file %s: error %d", (const char *)context, errno); } else { diff --git a/shell/android-studio/flycast/src/main/jni/src/http_client.h b/shell/android-studio/flycast/src/main/jni/src/http_client.h index 628fdb902..0d1505fdd 100644 --- a/shell/android-studio/flycast/src/main/jni/src/http_client.h +++ b/shell/android-studio/flycast/src/main/jni/src/http_client.h @@ -17,7 +17,7 @@ along with Flycast. If not, see . */ #pragma once -#include "rend/boxart/http_client.h" +#include "oslib/http_client.h" #include "jni_util.h" namespace http { diff --git a/shell/apple/common/http_client.mm b/shell/apple/common/http_client.mm index ec76832b3..7fd8e26ef 100644 --- a/shell/apple/common/http_client.mm +++ b/shell/apple/common/http_client.mm @@ -17,7 +17,7 @@ along with Flycast. If not, see . */ #import -#include "rend/boxart/http_client.h" +#include "oslib/http_client.h" namespace http { diff --git a/shell/uwp/http_client.cpp b/shell/uwp/http_client.cpp index 27f13e7ab..180bc5da1 100644 --- a/shell/uwp/http_client.cpp +++ b/shell/uwp/http_client.cpp @@ -16,7 +16,7 @@ You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ -#include "rend/boxart/http_client.h" +#include "oslib/http_client.h" #include "stdclass.h" namespace http { From 300cf0d437f27f01b9ef8e51819968e69c2d2646 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 1 May 2024 18:32:39 +0200 Subject: [PATCH 52/86] better RetroAchievements UI and threading Pop ups for authentication, game load, game completed, achievements unlock and progress. Handle disk changes. Issue #761 --- CMakeLists.txt | 6 +- core/achievements/achievements.cpp | 424 +++++++++++++----- core/achievements/achievements.h | 11 +- core/deps/imgui/imgui_stdlib.cpp | 85 ++++ core/deps/imgui/imgui_stdlib.h | 21 + core/emulator.cpp | 27 +- core/emulator.h | 6 +- core/hw/holly/sb.cpp | 1 + core/imgread/common.cpp | 1 + core/lua/lua.cpp | 3 + core/oslib/http_client.cpp | 23 +- core/oslib/http_client.h | 2 +- core/rend/gui.cpp | 71 +-- core/rend/gui_achievements.cpp | 171 +++++++ core/rend/gui_achievements.h | 56 +++ fonts/Roboto-Regular.ttf.zip | Bin 0 -> 88941 bytes .../com/reicast/emulator/emu/HttpClient.java | 4 +- .../flycast/src/main/jni/src/http_client.h | 6 +- shell/apple/common/http_client.mm | 8 +- shell/uwp/http_client.cpp | 8 +- 20 files changed, 754 insertions(+), 180 deletions(-) create mode 100644 core/deps/imgui/imgui_stdlib.cpp create mode 100644 core/deps/imgui/imgui_stdlib.h create mode 100644 core/rend/gui_achievements.cpp create mode 100644 core/rend/gui_achievements.h create mode 100644 fonts/Roboto-Regular.ttf.zip diff --git a/CMakeLists.txt b/CMakeLists.txt index ff6334a76..dd658157a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -712,6 +712,7 @@ if(NOT LIBRETRO) core/deps/imgui/imgui.cpp core/deps/imgui/imgui_demo.cpp core/deps/imgui/imgui_draw.cpp + core/deps/imgui/imgui_stdlib.cpp core/deps/imgui/imgui_tables.cpp core/deps/imgui/imgui_widgets.cpp) @@ -1012,7 +1013,9 @@ cmrc_add_resources(flycast-resources fonts/printer_kanji24x24.bin.zip) if(NOT LIBRETRO) - cmrc_add_resources(flycast-resources fonts/Roboto-Medium.ttf.zip) + cmrc_add_resources(flycast-resources + fonts/Roboto-Medium.ttf.zip + fonts/Roboto-Regular.ttf.zip) if(ANDROID) cmrc_add_resources(flycast-resources WHENCE resources @@ -1292,6 +1295,7 @@ if(NOT LIBRETRO) core/rend/imgui_driver.h core/rend/gui.cpp core/rend/gui.h + core/rend/gui_achievements.cpp core/rend/gui_android.cpp core/rend/gui_android.h core/rend/gui_chat.h diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp index 028c93ef1..6d544e184 100644 --- a/core/achievements/achievements.cpp +++ b/core/achievements/achievements.cpp @@ -20,9 +20,10 @@ #include "achievements.h" #include "serialize.h" #ifdef USE_RACHIEVEMENTS +#include "oslib/directory.h" #include "oslib/http_client.h" #include "hw/sh4/sh4_mem.h" -#include "rend/gui.h" +#include "rend/gui_achievements.h" #include "imgread/common.h" #include "cfg/option.h" #include "oslib/oslib.h" @@ -31,7 +32,11 @@ #include #include #include -#include +#include +#include +#include +#include +#include namespace achievements { @@ -43,7 +48,8 @@ public: ~Achievements(); bool init(); void term(); - void login(const char *username, const char *password); + std::future login(const char *username, const char *password); + void logout(); bool isLoggedOn() const { return loggedOn; } void serialize(Serializer& ser); void deserialize(Deserializer& deser); @@ -54,31 +60,42 @@ private: bool createClient(); std::string getGameHash(); void loadGame(); + void gameLoaded(int result, const char *errorMessage); void unloadGame(); void pauseGame(); void resumeGame(); + void loadCache(); + std::string getOrDownloadImage(const char *url); + void diskChange(); static void clientLoginWithTokenCallback(int result, const char *error_message, rc_client_t *client, void *userdata); static void clientLoginWithPasswordCallback(int result, const char *error_message, rc_client_t *client, void *userdata); + void authenticationSuccess(const rc_client_user_t *user); static void clientMessageCallback(const char *message, const rc_client_t *client); static u32 clientReadMemory(u32 address, u8 *buffer, u32 num_bytes, rc_client_t *client); static void clientServerCall(const rc_api_request_t *request, rc_client_server_callback_t callback, void *callback_data, rc_client_t *client); static void clientEventHandler(const rc_client_event_t *event, rc_client_t *client); - static void clientLoadGameCallback(int result, const char *error_message, rc_client_t *client, void *userdata); void handleResetEvent(const rc_client_event_t *event); void handleUnlockEvent(const rc_client_event_t *event); void handleAchievementChallengeIndicatorShowEvent(const rc_client_event_t *event); void handleAchievementChallengeIndicatorHideEvent(const rc_client_event_t *event); + void handleGameCompleted(const rc_client_event_t *event); + void handleShowAchievementProgress(const rc_client_event_t *event); + void handleHideAchievementProgress(const rc_client_event_t *event); + void handleUpdateAchievementProgress(const rc_client_event_t *event); static void emuEventCallback(Event event, void *arg); rc_client_t *rc_client = nullptr; bool loggedOn = false; + std::atomic_bool loadingGame {}; bool active = false; bool paused = false; std::string lastError; + std::future asyncServerCall; cResetEvent resetEvent; - std::thread thread; + std::string cachePath; + std::unordered_map cacheMap; }; bool init() @@ -91,9 +108,14 @@ void term() Achievements::Instance().term(); } -void login(const char *username, const char *password) +std::future login(const char *username, const char *password) { - Achievements::Instance().login(username, password); + return Achievements::Instance().login(username, password); +} + +void logout() +{ + Achievements::Instance().logout(); } bool isLoggedOn() @@ -124,6 +146,7 @@ Achievements::Achievements() EventManager::listen(Event::Terminate, emuEventCallback, this); EventManager::listen(Event::Pause, emuEventCallback, this); EventManager::listen(Event::Resume, emuEventCallback, this); + EventManager::listen(Event::DiskChange, emuEventCallback, this); } Achievements::~Achievements() @@ -132,6 +155,7 @@ Achievements::~Achievements() EventManager::unlisten(Event::Terminate, emuEventCallback, this); EventManager::unlisten(Event::Pause, emuEventCallback, this); EventManager::unlisten(Event::Resume, emuEventCallback, this); + EventManager::unlisten(Event::DiskChange, emuEventCallback, this); term(); } @@ -150,6 +174,7 @@ bool Achievements::init() //rc_client_set_encore_mode_enabled(rc_client, 0); //rc_client_set_unofficial_enabled(rc_client, 0); //rc_client_set_spectator_mode_enabled(rc_client, 0); + loadCache(); if (!config::AchievementsUserName.get().empty() && !config::AchievementsToken.get().empty()) { @@ -182,6 +207,58 @@ bool Achievements::createClient() return true; } +void Achievements::loadCache() +{ + cachePath = get_writable_data_path("achievements/"); + flycast::mkdir(cachePath.c_str(), 0755); + DIR *dir = flycast::opendir(cachePath.c_str()); + if (dir != nullptr) + { + while (true) + { + dirent *direntry = flycast::readdir(dir); + if (direntry == nullptr) + break; + std::string name = direntry->d_name; + if (name == "." || name == "..") + continue; + std::string s = get_file_basename(name); + u64 v = strtoull(s.c_str(), nullptr, 16); + cacheMap[v] = name; + } + } +} + +std::string Achievements::getOrDownloadImage(const char *url) +{ + u64 hash = XXH64(url, strlen(url), 13); + auto it = cacheMap.find(hash); + if (it != cacheMap.end()) + return cachePath + it->second; + std::vector content; + std::string content_type; + int rc = http::get(url, content, content_type); + if (!http::success(rc)) + return {}; + std::stringstream stream; + stream << std::hex << hash; + if (content_type == "image/jpeg") + stream << ".jpg"; + else + stream << ".png"; + std::string path = cachePath + stream.str(); + FILE *f = nowide::fopen(path.c_str(), "wb"); + if (f == nullptr) { + WARN_LOG(COMMON, "Can't save image to %s", path.c_str()); + return {}; + } + fwrite(content.data(), 1, content.size(), f); + fclose(f); + cacheMap[hash] = stream.str(); + DEBUG_LOG(COMMON, "RA: downloaded %s to %s", url, path.c_str()); + return path; +} + void Achievements::term() { if (rc_client == nullptr) @@ -191,48 +268,56 @@ void Achievements::term() rc_client = nullptr; } -static inline void authenticationSuccessMsg() +void Achievements::authenticationSuccess(const rc_client_user_t *user) { NOTICE_LOG(COMMON, "RA Login successful: token %s", config::AchievementsToken.get().c_str()); - std::string msg = "User " + config::AchievementsUserName.get() + " authenticated to RetroAchievements"; - gui_display_notification(msg.c_str(), 5000); + char url[512]; + int rc = rc_client_user_get_image_url(user, url, sizeof(url)); + if (rc == RC_OK) + { + std::string image = getOrDownloadImage(url); + std::string text = "User " + config::AchievementsUserName.get() + " authenticated"; + notifier.notify(Notification::Login, image, text); + } + loggedOn = true; + if (!settings.content.fileName.empty()) // TODO better test? + loadGame(); } -void Achievements::clientLoginWithTokenCallback(int result, const char* error_message, rc_client_t* client, - void* userdata) +void Achievements::clientLoginWithTokenCallback(int result, const char *error_message, rc_client_t *client, + void *userdata) { + Achievements *achievements = (Achievements *)rc_client_get_userdata(client); if (result != RC_OK) { WARN_LOG(COMMON, "RA Login failed: %s", error_message); - gui_display_notification(error_message, 10000); + notifier.notify(Notification::Login, "", "RetroAchievements authentication failed", error_message); return; } - - authenticationSuccessMsg(); - Achievements *achievements = (Achievements *)rc_client_get_userdata(client); - achievements->loggedOn = true; + achievements->authenticationSuccess(rc_client_get_user_info(client)); } -void Achievements::login(const char* username, const char* password) +std::future Achievements::login(const char* username, const char* password) { init(); - rc_client_begin_login_with_password(rc_client, username, password, clientLoginWithPasswordCallback, nullptr); - - if (!loggedOn) - throw FlycastException(lastError); + std::promise *promise = new std::promise(); + rc_client_begin_login_with_password(rc_client, username, password, clientLoginWithPasswordCallback, promise); + return promise->get_future(); } -void Achievements::clientLoginWithPasswordCallback(int result, const char* error_message, rc_client_t* client, - void* userdata) +void Achievements::clientLoginWithPasswordCallback(int result, const char *error_message, rc_client_t *client, + void *userdata) { Achievements *achievements = (Achievements *)rc_client_get_userdata(client); - + std::promise *promise = (std::promise *)userdata; if (result != RC_OK) { - achievements->lastError = rc_error_str(result); + std::string errorMsg = rc_error_str(result); if (error_message != nullptr) - achievements->lastError += ": " + std::string(error_message); - WARN_LOG(COMMON, "RA Login failed: %s", achievements->lastError.c_str()); + errorMsg += ": " + std::string(error_message); + promise->set_exception(std::make_exception_ptr(FlycastException(errorMsg))); + delete promise; + WARN_LOG(COMMON, "RA Login failed: %s", errorMsg.c_str()); return; } @@ -240,15 +325,27 @@ void Achievements::clientLoginWithPasswordCallback(int result, const char* error if (!user || !user->token) { WARN_LOG(COMMON, "RA: rc_client_get_user_info() returned NULL"); + promise->set_exception(std::make_exception_ptr(FlycastException("No user token returned"))); + delete promise; return; } // Store token in config config::AchievementsToken = user->token; SaveSettings(); - achievements->loggedOn = true; + achievements->authenticationSuccess(user); + promise->set_value(); + delete promise; +} - authenticationSuccessMsg(); +void Achievements::logout() +{ + unloadGame(); + rc_client_logout(rc_client); + // Reset token in config + config::AchievementsToken = ""; + SaveSettings(); + loggedOn = false; } void Achievements::clientMessageCallback(const char* message, const rc_client_t* client) @@ -282,27 +379,50 @@ u32 Achievements::clientReadMemory(u32 address, u8* buffer, u32 num_bytes, rc_cl return num_bytes; } -void Achievements::clientServerCall(const rc_api_request_t* request, rc_client_server_callback_t callback, - void* callback_data, rc_client_t* client) +void Achievements::clientServerCall(const rc_api_request_t *request, rc_client_server_callback_t callback, + void *callback_data, rc_client_t *client) { - int rc; - std::vector reply; + Achievements *achievements = (Achievements *)rc_client_get_userdata(client); + std::string url {request->url}; + std::string payload; if (request->post_data != nullptr) - rc = http::post(request->url, request->post_data, reply); - else - rc = http::get(request->url, reply); - rc_api_server_response_t rr; - rr.http_status_code = rc; // TODO RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR if connection fails? - rr.body_length = reply.size(); - rr.body = (const char *)reply.data(); - callback(&rr, callback_data); + payload = request->post_data; + std::string contentType; + if (request->content_type != nullptr) + contentType = request->content_type; + const auto& callServer = [url, contentType, payload, callback, callback_data]() + { + ThreadName _("Flycast-RA"); + int rc; + std::vector reply; + if (!payload.empty()) + rc = http::post(url, payload.c_str(), contentType.empty() ? nullptr : contentType.c_str(), reply); + else + rc = http::get(url, reply); + rc_api_server_response_t rr; + rr.http_status_code = rc; // TODO RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR if connection fails? + rr.body_length = reply.size(); + rr.body = (const char *)reply.data(); + callback(&rr, callback_data); + }; + if (achievements->asyncServerCall.valid()) + { + if (achievements->asyncServerCall.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) + { + INFO_LOG(COMMON, "Async server call already in progress"); + // process synchronously + callServer(); + return; + } + achievements->asyncServerCall.get(); + } + achievements->asyncServerCall = std::async(std::launch::async, callServer); } static void handleServerError(const rc_client_server_error_t* error) { - char buffer[256]; - snprintf(buffer, sizeof(buffer), "%s: %s", error->api, error->error_message); - gui_display_notification(buffer, 5000); + WARN_LOG(COMMON, "RA server error: %s - %s", error->api, error->error_message); + notifier.notify(Notification::Error, "", error->api, error->error_message); } void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_t* client) @@ -326,13 +446,26 @@ void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_ achievements->handleAchievementChallengeIndicatorHideEvent(event); break; + case RC_CLIENT_EVENT_GAME_COMPLETED: + achievements->handleGameCompleted(event); + break; + case RC_CLIENT_EVENT_SERVER_ERROR: handleServerError(event->server_error); break; + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW: + achievements->handleShowAchievementProgress(event); + break; + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE: + achievements->handleHideAchievementProgress(event); + break; + case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE: + achievements->handleUpdateAchievementProgress(event); + break; + /* TODO - case RC_CLIENT_EVENT_GAME_COMPLETED: case RC_CLIENT_EVENT_LEADERBOARD_STARTED: case RC_CLIENT_EVENT_LEADERBOARD_FAILED: case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED: @@ -340,10 +473,6 @@ void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_ case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW: case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE: case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE: - case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW: - case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE: - case RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE: - case RC_CLIENT_EVENT_SERVER_ERROR: case RC_CLIENT_EVENT_DISCONNECTED: case RC_CLIENT_EVENT_RECONNECTED: */ @@ -366,20 +495,60 @@ void Achievements::handleUnlockEvent(const rc_client_event_t *event) INFO_LOG(COMMON, "RA: Achievement %s (%u) for game %s unlocked", cheevo->title, cheevo->id, settings.content.title.c_str()); - std::string msg = "Achievement " + std::string(cheevo->title) + " unlocked!"; - gui_display_notification(msg.c_str(), 10000); + char url[512]; + int rc = rc_client_achievement_get_image_url(cheevo, cheevo->state, url, sizeof(url)); + if (rc == RC_OK) + { + std::string image = getOrDownloadImage(url); + std::string text = "Achievement " + std::string(cheevo->title) + " unlocked!"; + notifier.notify(Notification::Login, image, text, cheevo->description); + } } void Achievements::handleAchievementChallengeIndicatorShowEvent(const rc_client_event_t *event) { // TODO there might be more than one. Need to display an icon. - std::string msg = "Challenge: " + std::string(event->achievement->title); - gui_display_notification(msg.c_str(), 10000); + //std::string msg = "Challenge: " + std::string(event->achievement->title); + //gui_display_notification(msg.c_str(), 10000); + INFO_LOG(COMMON, "RA: Challenge: %s", event->achievement->title); } void Achievements::handleAchievementChallengeIndicatorHideEvent(const rc_client_event_t *event) { - gui_display_notification("", 1); + // TODO +} + +void Achievements::handleGameCompleted(const rc_client_event_t *event) +{ + const rc_client_game_t* game = rc_client_get_game_info(rc_client); + std::string image; + char url[128]; + if (rc_client_game_get_image_url(game, url, sizeof(url)) == RC_OK) + image = getOrDownloadImage(url); + std::string text1 = (rc_client_get_hardcore_enabled(rc_client) ? "Mastered " : "Completed ") + std::string(game->title); + rc_client_user_game_summary_t summary; + rc_client_get_user_game_summary(rc_client, &summary); + std::stringstream ss; + ss << summary.num_unlocked_achievements << " achievements, " << summary.points_unlocked << " points"; + std::string text3 = rc_client_get_user_info(rc_client)->display_name; + notifier.notify(Notification::Mastery, image, text1, ss.str(), text3); +} + +void Achievements::handleShowAchievementProgress(const rc_client_event_t *event) +{ + handleUpdateAchievementProgress(event); +} +void Achievements::handleHideAchievementProgress(const rc_client_event_t *event) +{ + notifier.notify(Notification::Progress, "", ""); +} +void Achievements::handleUpdateAchievementProgress(const rc_client_event_t *event) +{ + char url[128]; + std::string image; + if (rc_client_achievement_get_image_url(event->achievement, RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE, url, sizeof(url)) == RC_OK) + image = getOrDownloadImage(url); + notifier.notify(Notification::Progress, image, event->achievement->measured_progress); } static bool add150; @@ -419,6 +588,7 @@ static size_t cdreader_read_sector(void* track_handle, u32 sector, void* buffer, { //DEBUG_LOG(COMMON, "RA: cdreader_read_sector track %zd sec %d num %zd", reinterpret_cast(track_handle), sector, requested_bytes); if (requested_bytes == 2048) + // add 150 sectors to FAD corresponding to files add150 = true; if (add150) // FIXME how to get rid of this? sector += 150; @@ -457,6 +627,9 @@ std::string Achievements::getGameHash() { if (settings.platform.isConsole()) { + const u32 diskType = libGDR_GetDiscType(); + if (diskType == NoDisk || diskType == Open) + return ""; add150 = false; rc_hash_cdreader hooks = { cdreader_open_track, @@ -485,12 +658,27 @@ void Achievements::pauseGame() { paused = true; if (active) + { resetEvent.Reset(); + if (asyncServerCall.valid()) + asyncServerCall.get(); + asyncServerCall = std::async(std::launch::async, [this]() { + while (paused) + { + resetEvent.Wait(1000); + if (paused) + rc_client_idle(rc_client); + } + }); + } } void Achievements::resumeGame() { paused = false; + resetEvent.Set(); + if (asyncServerCall.valid()) + asyncServerCall.get(); if (config::EnableAchievements) loadGame(); else @@ -510,11 +698,14 @@ void Achievements::emuEventCallback(Event event, void *arg) instance->unloadGame(); break; case Event::VBlank: - instance->resetEvent.Set(); + rc_client_do_frame(instance->rc_client); break; case Event::Pause: instance->pauseGame(); break; + case Event::DiskChange: + instance->diskChange(); + break; default: break; } @@ -522,34 +713,67 @@ void Achievements::emuEventCallback(Event event, void *arg) void Achievements::loadGame() { + if (loadingGame.exchange(true)) + // already loading + return; if (active) + { // already loaded + loadingGame = false; return; - if (!init() || !isLoggedOn()) + } + if (!init() || !isLoggedOn()) { + if (!isLoggedOn()) + WARN_LOG(COMMON, "Not logged on. Not loading game yet"); + loadingGame = false; return; + } std::string gameHash = getGameHash(); if (!gameHash.empty()) - rc_client_begin_load_game(rc_client, gameHash.c_str(), clientLoadGameCallback, nullptr); - if (!active) - return; - thread = std::thread([this] { - ThreadName _("Flycast-RA"); - while (active) - { - if (!resetEvent.Wait(1000)) { - if (paused) { - DEBUG_LOG(COMMON, "RA: rc_client_idle"); - rc_client_idle(rc_client); - } - else { - INFO_LOG(COMMON, "RA: timeout on event and not paused!"); - } - } - else if (active) - rc_client_do_frame(rc_client); + { + rc_client_begin_load_game(rc_client, gameHash.c_str(), [](int result, const char *error_message, rc_client_t *client, void *userdata) { + ((Achievements *)userdata)->gameLoaded(result, error_message); + }, this); + } +} + +void Achievements::gameLoaded(int result, const char *errorMessage) +{ + if (result != RC_OK) + { + if (result == RC_NO_GAME_LOADED) + // Unknown game. + INFO_LOG(COMMON, "RA: Unknown game, disabling achievements."); + else if (result == RC_LOGIN_REQUIRED) { + // We would've asked to re-authenticate, so leave HC on for now. + // Once we've done so, we'll reload the game. } - }); + else + WARN_LOG(COMMON, "RA Loading game failed: %s", errorMessage); + loadingGame = false; + return; + } + const rc_client_game_t* info = rc_client_get_game_info(rc_client); + if (info == nullptr) + { + WARN_LOG(COMMON, "RA: rc_client_get_game_info() returned NULL"); + loadingGame = false; + return; + } + active = true; + loadingGame = false; EventManager::listen(Event::VBlank, emuEventCallback, this); + NOTICE_LOG(COMMON, "RA: game %d loaded: %s, achievements %d leaderboards %d rich presence %d", info->id, info->title, + rc_client_has_achievements(rc_client), rc_client_has_leaderboards(rc_client), rc_client_has_rich_presence(rc_client)); + std::string image; + char url[512]; + if (rc_client_game_get_image_url(info, url, sizeof(url)) == RC_OK) + image = getOrDownloadImage(url); + rc_client_user_game_summary_t summary; + rc_client_get_user_game_summary(rc_client, &summary); + std::string text = "You have " + std::to_string(summary.num_unlocked_achievements) + + " of " + std::to_string(summary.num_core_achievements) + " achievements unlocked"; + notifier.notify(Notification::Login, image, info->title, text); } void Achievements::unloadGame() @@ -557,42 +781,34 @@ void Achievements::unloadGame() if (!active) return; active = false; + paused = false; resetEvent.Set(); - thread.join(); + if (asyncServerCall.valid()) + asyncServerCall.get(); EventManager::unlisten(Event::VBlank, emuEventCallback, this); rc_client_unload_game(rc_client); } -void Achievements::clientLoadGameCallback(int result, const char* error_message, rc_client_t* client, void* userdata) +void Achievements::diskChange() { - Achievements *achiev = (Achievements *)rc_client_get_userdata(client); - if (result == RC_NO_GAME_LOADED) - { - // Unknown game. - INFO_LOG(COMMON, "RA: Unknown game, disabling achievements."); + if (!active) + return; + std::string hash = getGameHash(); + if (hash == "") { + unloadGame(); return; } - if (result == RC_LOGIN_REQUIRED) - { - // We would've asked to re-authenticate, so leave HC on for now. - // Once we've done so, we'll reload the game. - return; - } - if (result != RC_OK) - { - WARN_LOG(COMMON, "RA Loading game failed: %s", error_message); - return; - } - const rc_client_game_t* info = rc_client_get_game_info(client); - if (info == nullptr) - { - WARN_LOG(COMMON, "RA: rc_client_get_game_info() returned NULL"); - return; - } - // TODO? rc_client_game_get_image_url(info, buf, std::size(buf)); - achiev->active = true; - NOTICE_LOG(COMMON, "RA: game %d loaded: %s, achievements %d leaderboards %d rich presence %d", info->id, info->title, - rc_client_has_achievements(client), rc_client_has_leaderboards(client), rc_client_has_rich_presence(client)); + rc_client_begin_change_media_from_hash(rc_client, hash.c_str(), [](int result, const char *errorMessage, rc_client_t *client, void *userdata) { + if (result == RC_HARDCORE_DISABLED) { + notifier.notify(Notification::Login, "", "Hardcore disabled", "Unrecognized media inserted"); + } + else if (result != RC_OK) + { + if (errorMessage == nullptr) + errorMessage = rc_error_str(result); + notifier.notify(Notification::Login, "", "Media change failed", errorMessage); + } + }, this); } void Achievements::serialize(Serializer& ser) diff --git a/core/achievements/achievements.h b/core/achievements/achievements.h index e029ff1b9..28023cc28 100644 --- a/core/achievements/achievements.h +++ b/core/achievements/achievements.h @@ -16,6 +16,7 @@ */ #pragma once #include "types.h" +#include namespace achievements { @@ -23,16 +24,10 @@ namespace achievements bool init(); void term(); -void login(const char *username, const char *password); +std::future login(const char *username, const char *password); +void logout(); bool isLoggedOn(); -#else - -static inline bool init() { return false; } -static inline void term() {} -static inline void login(const char *username, const char *password) {} -static inline bool isLoggedOn() { return false; } - #endif void serialize(Serializer& ser); diff --git a/core/deps/imgui/imgui_stdlib.cpp b/core/deps/imgui/imgui_stdlib.cpp new file mode 100644 index 000000000..cf69aa89a --- /dev/null +++ b/core/deps/imgui/imgui_stdlib.cpp @@ -0,0 +1,85 @@ +// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.) +// This is also an example of how you may wrap your own similar types. + +// Changelog: +// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string + +// See more C++ related extension (fmt, RAII, syntaxis sugar) on Wiki: +// https://github.com/ocornut/imgui/wiki/Useful-Extensions#cness + +#include "imgui.h" +#include "imgui_stdlib.h" + +// Clang warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#endif + +struct InputTextCallback_UserData +{ + std::string* Str; + ImGuiInputTextCallback ChainCallback; + void* ChainCallbackUserData; +}; + +static int InputTextCallback(ImGuiInputTextCallbackData* data) +{ + InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData; + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) + { + // Resize string callback + // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want. + std::string* str = user_data->Str; + IM_ASSERT(data->Buf == str->c_str()); + str->resize(data->BufTextLen); + data->Buf = (char*)str->c_str(); + } + else if (user_data->ChainCallback) + { + // Forward to user callback, if any + data->UserData = user_data->ChainCallbackUserData; + return user_data->ChainCallback(data); + } + return 0; +} + +bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.ChainCallback = callback; + cb_user_data.ChainCallbackUserData = user_data; + return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); +} + +bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.ChainCallback = callback; + cb_user_data.ChainCallbackUserData = user_data; + return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data); +} + +bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) +{ + IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0); + flags |= ImGuiInputTextFlags_CallbackResize; + + InputTextCallback_UserData cb_user_data; + cb_user_data.Str = str; + cb_user_data.ChainCallback = callback; + cb_user_data.ChainCallbackUserData = user_data; + return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data); +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif diff --git a/core/deps/imgui/imgui_stdlib.h b/core/deps/imgui/imgui_stdlib.h new file mode 100644 index 000000000..835a808f2 --- /dev/null +++ b/core/deps/imgui/imgui_stdlib.h @@ -0,0 +1,21 @@ +// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.) +// This is also an example of how you may wrap your own similar types. + +// Changelog: +// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string + +// See more C++ related extension (fmt, RAII, syntaxis sugar) on Wiki: +// https://github.com/ocornut/imgui/wiki/Useful-Extensions#cness + +#pragma once + +#include + +namespace ImGui +{ + // ImGui::InputText() with std::string + // Because text input needs dynamic resizing, we need to setup a callback to grow the capacity + IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); + IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); + IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = nullptr, void* user_data = nullptr); +} diff --git a/core/emulator.cpp b/core/emulator.cpp index 03da7a54c..bfdd7a219 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -772,33 +772,22 @@ void Emulator::setNetworkState(bool online) void EventManager::registerEvent(Event event, Callback callback, void *param) { unregisterEvent(event, callback, param); - auto it = callbacks.find(event); - if (it != callbacks.end()) - it->second.push_back(std::make_pair(callback, param)); - else - callbacks.insert({ event, { std::make_pair(callback, param) } }); + auto& vector = callbacks[static_cast(event)]; + vector.push_back(std::make_pair(callback, param)); } void EventManager::unregisterEvent(Event event, Callback callback, void *param) { - auto it = callbacks.find(event); - if (it == callbacks.end()) - return; - - auto it2 = std::find(it->second.begin(), it->second.end(), std::make_pair(callback, param)); - if (it2 == it->second.end()) - return; - - it->second.erase(it2); + auto& vector = callbacks[static_cast(event)]; + auto it = std::find(vector.begin(), vector.end(), std::make_pair(callback, param)); + if (it != vector.end()) + vector.erase(it); } void EventManager::broadcastEvent(Event event) { - auto it = callbacks.find(event); - if (it == callbacks.end()) - return; - - for (auto& pair : it->second) + auto& vector = callbacks[static_cast(event)]; + for (auto& pair : vector) pair.first(event, pair.second); } diff --git a/core/emulator.h b/core/emulator.h index 96c7d36c8..f4b8b14c0 100644 --- a/core/emulator.h +++ b/core/emulator.h @@ -23,7 +23,7 @@ #include #include -#include +#include #include #include #include @@ -47,6 +47,8 @@ enum class Event { LoadState, VBlank, Network, + DiskChange, + max = DiskChange }; class EventManager @@ -77,7 +79,7 @@ private: void unregisterEvent(Event event, Callback callback, void *param); void broadcastEvent(Event event); - std::map>> callbacks; + std::array>, static_cast(Event::max) + 1> callbacks; }; struct LoadProgress diff --git a/core/hw/holly/sb.cpp b/core/hw/holly/sb.cpp index 49e47b1df..5966141d4 100644 --- a/core/hw/holly/sb.cpp +++ b/core/hw/holly/sb.cpp @@ -14,6 +14,7 @@ #include "emulator.h" #include "hw/bba/bba.h" #include "serialize.h" +#include u32 sb_regs[0x540]; HollyRegisters hollyRegs; diff --git a/core/imgread/common.cpp b/core/imgread/common.cpp index 6aa6853f5..b99ed778f 100644 --- a/core/imgread/common.cpp +++ b/core/imgread/common.cpp @@ -350,6 +350,7 @@ bool DiscSwap(const std::string& path) { if (!doDiscSwap(path)) throw FlycastException("This media cannot be loaded"); + EventManager::event(Event::DiskChange); // Drive is busy after the lid was closed sns_asc = 4; sns_ascq = 1; diff --git a/core/lua/lua.cpp b/core/lua/lua.cpp index 303543d0a..7ad7e5ce3 100644 --- a/core/lua/lua.cpp +++ b/core/lua/lua.cpp @@ -74,6 +74,9 @@ static void emuEventCallback(Event event, void *) case Event::Network: key = "network"; break; + case Event::DiskChange: + key = "diskChange"; + break; } if (v[key].isFunction()) v[key](); diff --git a/core/oslib/http_client.cpp b/core/oslib/http_client.cpp index 52b707743..3742159b1 100644 --- a/core/oslib/http_client.cpp +++ b/core/oslib/http_client.cpp @@ -98,10 +98,7 @@ static int post(const std::string& url, const char *headers, const u8 *payload, if (payloadSize > 0) { char clen[128]; - if (headers == nullptr) - snprintf(clen, sizeof(clen), "Content-Length: %d\r\nContent-Type: application/x-www-form-urlencoded\r\n", payloadSize); - else - snprintf(clen, sizeof(clen), "Content-Length: %d\r\n", payloadSize); + snprintf(clen, sizeof(clen), "Content-Length: %d\r\n", payloadSize); HttpAddRequestHeaders(hreq, clen, -1L, HTTP_ADDREQ_FLAG_ADD_IF_NEW); } if (!HttpSendRequest(hreq, headers, -1, (void *)payload, payloadSize)) @@ -141,9 +138,14 @@ static int post(const std::string& url, const char *headers, const u8 *payload, return rc; } -int post(const std::string& url, const char *payload, std::vector& reply) +int post(const std::string& url, const char *payload, const char *contentType, std::vector& reply) { - return post(url, nullptr, (const u8 *)payload, strlen(payload), reply); + char buf[512]; + if (contentType != nullptr) { + sprintf(buf, "Content-Type: %s", contentType); + contentType = buf; + } + return post(url, contentType, (const u8 *)payload, strlen(payload), reply); } int post(const std::string& url, const std::vector& fields) @@ -272,13 +274,19 @@ int get(const std::string& url, std::vector& content, std::string& contentTy return (int)httpCode; } -int post(const std::string& url, const char *payload, std::vector& reply) +int post(const std::string& url, const char *payload, const char *contentType, std::vector& reply) { CURL *curl = makeCurlEasy(url); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); curl_easy_setopt(curl, CURLOPT_POST, 1); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, payload); + curl_slist *headers = nullptr; + if (contentType != nullptr) + { + headers = curl_slist_append(headers, ("Content-Type: " + std::string(contentType)).c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } std::vector recvBuffer; curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData); @@ -291,6 +299,7 @@ int post(const std::string& url, const char *payload, std::vector& reply) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode); reply = recvBuffer; } + curl_slist_free_all(headers); curl_easy_cleanup(curl); return (int)httpCode; diff --git a/core/oslib/http_client.h b/core/oslib/http_client.h index f4493670d..c71fb668f 100644 --- a/core/oslib/http_client.h +++ b/core/oslib/http_client.h @@ -50,7 +50,7 @@ struct PostField }; int post(const std::string& url, const std::vector& fields); -int post(const std::string& url, const char *payload, std::vector& reply); +int post(const std::string& url, const char *payload, const char *contentType, std::vector& reply); static inline bool success(int status) { return status >= 200 && status < 300; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index eca4e892a..e24c1e033 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -22,6 +22,7 @@ #include "hw/maple/maple_if.h" #include "hw/maple/maple_devs.h" #include "imgui.h" +#include "imgui_stdlib.h" #include "network/net_handshake.h" #include "network/ggpo.h" #include "wsi/context.h" @@ -46,6 +47,7 @@ #include "hw/naomi/card_reader.h" #include "oslib/resources.h" #include "achievements/achievements.h" +#include "gui_achievements.h" #if defined(USE_SDL) #include "sdl/sdl.h" #endif @@ -95,6 +97,8 @@ static Chat chat; static std::recursive_mutex guiMutex; using LockGuard = std::lock_guard; +ImFont *largeFont; + static void emuEventCallback(Event event, void *) { switch (event) @@ -215,6 +219,7 @@ void gui_initFonts() ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); + largeFont = nullptr; const float fontSize = 17.f * settings.display.uiScale; size_t dataSize; std::unique_ptr data = resource::load("fonts/Roboto-Medium.ttf", dataSize); @@ -304,7 +309,13 @@ void gui_initFonts() // TODO Linux, iOS, ... #endif - NOTICE_LOG(RENDERER, "Screen DPI is %.0f, size %d x %d. Scaling by %.2f", settings.display.dpi, settings.display.width, settings.display.height, settings.display.uiScale); + // Large font without Asian glyphs + data = resource::load("fonts/Roboto-Regular.ttf", dataSize); + verify(data != nullptr); + const float largeFontSize = 21.f * settings.display.uiScale; + largeFont = io.Fonts->AddFontFromMemoryTTF(data.release(), dataSize, largeFontSize, nullptr, ranges); + + NOTICE_LOG(RENDERER, "Screen DPI is %.0f, size %d x %d. Scaling by %.2f", settings.display.dpi, settings.display.width, settings.display.height, settings.display.uiScale); } void gui_keyboard_input(u16 wc) @@ -1744,26 +1755,42 @@ static void gui_display_settings() { DisabledScope _(!config::EnableAchievements); ImGui::Indent(); - char username[256]; - strcpy(username, config::AchievementsUserName.get().c_str()); - ImGui::InputText("Username", username, sizeof(username), ImGuiInputTextFlags_None, nullptr, nullptr); - config::AchievementsUserName = username; + ImGui::InputText("Username", &config::AchievementsUserName.get(), + achievements::isLoggedOn() ? ImGuiInputTextFlags_ReadOnly : ImGuiInputTextFlags_None, nullptr, nullptr); if (config::EnableAchievements) { + static std::future futureLogin; achievements::init(); if (achievements::isLoggedOn()) + { ImGui::Text("Authentication successful"); + if (futureLogin.valid()) + futureLogin.get(); + if (ImGui::Button("Logout", ScaledVec2(100, 0))) + achievements::logout(); + } else { static char password[256]; ImGui::InputText("Password", password, sizeof(password), ImGuiInputTextFlags_Password, nullptr, nullptr); - if (ImGui::Button("Login", ScaledVec2(100, 0))) + if (futureLogin.valid()) { - try { - achievements::login(config::AchievementsUserName.get().c_str(), password); - } catch (const FlycastException& e) { - gui_error(e.what()); + if (futureLogin.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) { + ImGui::Text("Authenticating..."); } + else + { + try { + futureLogin.get(); + } catch (const FlycastException& e) { + gui_error(e.what()); + } + } + } + if (ImGui::Button("Login", ScaledVec2(100, 0)) && !futureLogin.valid()) + { + futureLogin = achievements::login(config::AchievementsUserName.get().c_str(), password); + memset(password, 0, sizeof(password)); } } } @@ -2569,12 +2596,9 @@ static void gui_display_settings() config::NetworkEnable = false; OptionCheckbox("Play as Player 1", config::ActAsServer, "Deselect to play as player 2"); - char server_name[256]; - strcpy(server_name, config::NetworkServer.get().c_str()); - ImGui::InputText("Peer", server_name, sizeof(server_name), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::InputText("Peer", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("Your peer IP address and optional port"); - config::NetworkServer.set(server_name); OptionSlider("Frame Delay", config::GGPODelay, 0, 20, "Sets Frame Delay, advisable for sessions with ping >100 ms"); @@ -2608,12 +2632,9 @@ static void gui_display_settings() "Create a local server for Naomi network games"); if (!config::ActAsServer) { - char server_name[256]; - strcpy(server_name, config::NetworkServer.get().c_str()); - ImGui::InputText("Server", server_name, sizeof(server_name), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::InputText("Server", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("The server to connect to. Leave blank to find a server automatically on the default port"); - config::NetworkServer.set(server_name); } char localPort[256]; sprintf(localPort, "%d", (int)config::LocalPort); @@ -2624,12 +2645,9 @@ static void gui_display_settings() } else if (config::BattleCableEnable) { - char server_name[256]; - strcpy(server_name, config::NetworkServer.get().c_str()); - ImGui::InputText("Peer", server_name, sizeof(server_name), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::InputText("Peer", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("The peer to connect to. Leave blank to find a player automatically on the default port"); - config::NetworkServer.set(server_name); char localPort[256]; sprintf(localPort, "%d", (int)config::LocalPort); ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); @@ -2721,14 +2739,9 @@ static void gui_display_settings() #ifdef USE_LUA header("Lua Scripting"); { - char LuaFileName[256]; - - strcpy(LuaFileName, config::LuaFileName.get().c_str()); - ImGui::InputText("Lua Filename", LuaFileName, sizeof(LuaFileName), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::InputText("Lua Filename", &config::LuaFileName.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("Specify lua filename to use. Should be located in Flycast config directory. Defaults to flycast.lua when empty."); - config::LuaFileName = LuaFileName; - } #endif } @@ -3416,7 +3429,7 @@ void gui_display_osd() gui_newFrame(); ImGui::NewFrame(); - if (!message.empty()) + if (!achievements::notifier.draw() && !message.empty()) { ImGui::SetNextWindowBgAlpha(0); ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetIO().DisplaySize.y), ImGuiCond_Always, ImVec2(0.f, 1.f)); // Lower left corner diff --git a/core/rend/gui_achievements.cpp b/core/rend/gui_achievements.cpp new file mode 100644 index 000000000..97ab1d00e --- /dev/null +++ b/core/rend/gui_achievements.cpp @@ -0,0 +1,171 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#include "gui_achievements.h" +#include "gui.h" +#include "gui_util.h" +#include "imgui_driver.h" +#include "stdclass.h" +#include + +extern ImFont *largeFont; + +namespace achievements +{ + +Notification notifier; + +static constexpr u64 DISPLAY_TIME = 5000; +static constexpr u64 START_ANIM_TIME = 500; +static constexpr u64 END_ANIM_TIME = 1000; + +void Notification::notify(Type type, const std::string& image, const std::string& text1, + const std::string& text2, const std::string& text3) +{ + std::lock_guard _(mutex); + u64 now = getTimeMs(); + if (type == Progress) + { + if (!text1.empty()) + { + if (this->type == None) + { + // New progress + startTime = now; + endTime = 0x1000000000000; // never + } + } + else + { + // Hide progress + endTime = now; + } + } + else { + startTime = now; + endTime = startTime + DISPLAY_TIME; + } + this->type = type; + this->imagePath = image; + this->imageId = {}; + text[0] = text1; + text[1] = text2; + text[2] = text3; +} + +bool Notification::draw() +{ + std::lock_guard _(mutex); + if (type == None) + return false; + u64 now = getTimeMs(); + if (now > endTime + END_ANIM_TIME) { + // Hide notification + type = None; + return false; + } + if (now > endTime) + { + // Fade out + float alpha = (std::cos((now - endTime) / (float)END_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; + ImGui::GetStyle().Alpha = alpha; + ImGui::SetNextWindowBgAlpha(alpha / 2.f); + } + else { + ImGui::SetNextWindowBgAlpha(0.5f); + } + if (imageId == ImTextureID{}) + getImage(); + float y = ImGui::GetIO().DisplaySize.y; + if (now - startTime < START_ANIM_TIME) + // Slide up + y += 80.f * settings.display.uiScale * (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; + + ImGui::SetNextWindowPos(ImVec2(0, y), ImGuiCond_Always, ImVec2(0.f, 1.f)); // Lower left corner + ImGui::SetNextWindowSizeConstraints(ScaledVec2(80.f, 80.f) + ImVec2(ImGui::GetStyle().WindowPadding.x * 2, 0.f), ImVec2(FLT_MAX, FLT_MAX)); + const float winPaddingX = ImGui::GetStyle().WindowPadding.x; + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{}); + + ImGui::Begin("##achievements", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav + | ImGuiWindowFlags_NoInputs); + const bool hasPic = imageId != ImTextureID{}; + if (ImGui::BeginTable("achievementNotif", hasPic ? 2 : 1, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) + { + if (hasPic) + ImGui::TableSetupColumn("icon", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("text", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + if (hasPic) + { + ImGui::Image(imageId, ScaledVec2(80.f, 80.f), { 0.f, 0.f }, { 1.f, 1.f }); + ImGui::TableSetColumnIndex(1); + } + + float w = largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, -1.f, text[0].c_str()).x; + w = std::max(w, ImGui::CalcTextSize(text[1].c_str()).x); + w = std::max(w, ImGui::CalcTextSize(text[2].c_str()).x) + winPaddingX * 2; + int lines = (int)!text[0].empty() + (int)!text[1].empty() + (int)!text[2].empty(); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ hasPic ? 0.f : winPaddingX, (3 - lines) * ImGui::GetTextLineHeight() / 2 }); + if (ImGui::BeginChild("##text", ImVec2(w, 0), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_None)) + { + ImGui::PushFont(largeFont); + ImGui::Text("%s", text[0].c_str()); + ImGui::PopFont(); + if (!text[1].empty()) + ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", text[1].c_str()); + if (!text[2].empty()) + ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", text[2].c_str()); + } + ImGui::EndChild(); + ImGui::PopStyleVar(); + + ImGui::EndTable(); + } + ImGui::End(); + ImGui::PopStyleVar(); + ImGui::GetStyle().Alpha = 1.f; + + return true; +} + +void Notification::getImage() +{ + if (imagePath.empty()) + return; + + // Get the texture. Load it if needed. + imageId = imguiDriver->getTexture(imagePath); + if (imageId == ImTextureID()) + { + int width, height; + u8 *imgData = loadImage(imagePath, width, height); + if (imgData != nullptr) + { + try { + imageId = imguiDriver->updateTextureAndAspectRatio(imagePath, imgData, width, height); + } catch (...) { + // vulkan can throw during resizing + } + free(imgData); + } + } +} + +} // namespace achievements diff --git a/core/rend/gui_achievements.h b/core/rend/gui_achievements.h new file mode 100644 index 000000000..009581f20 --- /dev/null +++ b/core/rend/gui_achievements.h @@ -0,0 +1,56 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#include "types.h" +#include "imgui.h" +#include + +namespace achievements +{ + +class Notification +{ +public: + enum Type { + None, + Login, + GameLoaded, + Unlocked, + Progress, + Mastery, + Error + }; + void notify(Type type, const std::string& image, const std::string& text1, + const std::string& text2 = {}, const std::string& text3 = {}); + bool draw(); + +private: + void getImage(); + + u64 startTime = 0; + u64 endTime = 0; + Type type = Type::None; + std::string imagePath; + ImTextureID imageId {}; + std::string text[3]; + std::mutex mutex; +}; + +extern Notification notifier; + +} diff --git a/fonts/Roboto-Regular.ttf.zip b/fonts/Roboto-Regular.ttf.zip new file mode 100644 index 0000000000000000000000000000000000000000..6bc0533e88416a24a6ec1f6689c46f9c6934e2fb GIT binary patch literal 88941 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

$={eLlYWFGRS{UpsG(b3%^EH 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 reply) + int post(const std::string &url, const char *payload, const char *contentType, std::vector& reply) { jni::String jurl(url); jni::String jpayload(payload); + jni::String jcontentType(contentType); jni::ObjectArray replyOut(1); int httpStatus = jni::env()->CallIntMethod(HttpClient, postRawMid, static_cast(jurl), static_cast(jpayload), + static_cast(jcontentType), static_cast(replyOut)); reply = replyOut[0]; @@ -95,5 +97,5 @@ extern "C" JNIEXPORT void JNICALL Java_com_reicast_emulator_emu_HttpClient_nativ http::HttpClient = env->NewGlobalRef(obj); http::openUrlMid = env->GetMethodID(env->GetObjectClass(obj), "openUrl", "(Ljava/lang/String;[[B[Ljava/lang/String;)I"); http::postMid = env->GetMethodID(env->GetObjectClass(obj), "post", "(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;)I"); - http::postRawMid = env->GetMethodID(env->GetObjectClass(obj), "post", "(Ljava/lang/String;Ljava/lang/String;[[B)I"); + http::postRawMid = env->GetMethodID(env->GetObjectClass(obj), "post", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[[B)I"); } diff --git a/shell/apple/common/http_client.mm b/shell/apple/common/http_client.mm index 7fd8e26ef..e577face7 100644 --- a/shell/apple/common/http_client.mm +++ b/shell/apple/common/http_client.mm @@ -46,7 +46,7 @@ int get(const std::string& url, std::vector& content, std::string& contentTy return [httpResponse statusCode]; } -int post(const std::string& url, const char *payload, std::vector& reply) +int post(const std::string& url, const char *payload, const char *contentType, std::vector& reply) { NSString *nsurl = [NSString stringWithCString:url.c_str() encoding:[NSString defaultCStringEncoding]]; @@ -59,8 +59,10 @@ int post(const std::string& url, const char *payload, std::vector& reply) [request setHTTPBody:[NSData dataWithBytes:payload length:payloadSize]]; NSString *postLength = [NSString stringWithFormat:@"%ld", (unsigned long)payloadSize]; [request setValue:postLength forHTTPHeaderField:@"Content-Length"]; - NSString *contentType = @"application/x-www-form-urlencoded"; - [request setValue:contentType forHTTPHeaderField:@"Content-Type"]; + NSString *nscontentType = contentType != nullptr ? [NSString stringWithCString:contentType + encoding:[NSString defaultCStringEncoding]] + : @"application/x-www-form-urlencoded"; + [request setValue:nscontentType forHTTPHeaderField:@"Content-Type"]; NSURLResponse *response = nil; NSError *error = nil; diff --git a/shell/uwp/http_client.cpp b/shell/uwp/http_client.cpp index 180bc5da1..68de14302 100644 --- a/shell/uwp/http_client.cpp +++ b/shell/uwp/http_client.cpp @@ -89,7 +89,7 @@ int get(const std::string& url, std::vector& content, std::string& contentTy } } -int post(const std::string& url, const char *payload, std::vector& reply) +int post(const std::string& url, const char *payload, const char *contentType, std::vector& reply) { nowide::wstackstring wurl; if (!wurl.convert(url.c_str())) @@ -97,12 +97,16 @@ int post(const std::string& url, const char *payload, std::vector& reply) nowide::wstackstring wpayload; if (!wpayload.convert(payload)) return 500; + nowide::wstackstring wcontentType; + if (contentType != nullptr && !wcontentType.convert(contentType)) + return 500; try { Uri^ uri = ref new Uri(ref new String(wurl.get())); HttpStringContent^ content = ref new HttpStringContent(ref new String(wpayload.get())); content->Headers->ContentLength = strlen(payload); - content->Headers->ContentType = ref new HttpMediaTypeHeaderValue("application/x-www-form-urlencoded"); + if (contentType != nullptr) + content->Headers->ContentType = ref new HttpMediaTypeHeaderValue(ref new String(wcontentType.get())); IAsyncOperationWithProgress^ op = httpClient->PostAsync(uri, content); cResetEvent asyncEvent; From 2d4684a4625ca61c25d8358d2c425dbc533b0095 Mon Sep 17 00:00:00 2001 From: Edward Li Date: Wed, 1 May 2024 19:28:03 +0800 Subject: [PATCH 53/86] Use Xcode 15.3 --- .github/workflows/c-cpp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 63d68327e..78120d27b 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -29,6 +29,7 @@ jobs: steps: - name: Set up build environment (macOS) run: | + sudo xcode-select -switch /Applications/Xcode_15.3.app # Unlink and re-link to prevent errors when github mac runner images install python outside of brew. See https://github.com/actions/setup-python/issues/577 brew list -1 | grep python | while read formula; do brew unlink $formula; brew link --overwrite $formula; done brew update || : From fe17d459a54ab072c700aa76f2d15d51c5165d4f Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Fri, 3 May 2024 20:09:31 +0200 Subject: [PATCH 54/86] ui: achievements list, new pause menu Fix threading issue when hashing disk for RA. Protect cacheMap with mutex. Achievement current challenges displayed as small icons. Embed FontAwesome symbols. New pause menu. Issue #761 --- CMakeLists.txt | 6 +- core/achievements/achievements.cpp | 265 ++++-- core/achievements/achievements.h | 26 + core/emulator.h | 1 + core/nullDC.cpp | 30 + core/oslib/storage.cpp | 3 +- core/rend/CustomTexture.cpp | 2 +- core/rend/IconsFontAwesome6.h | 1406 ++++++++++++++++++++++++++++ core/rend/gui.cpp | 313 ++++--- core/rend/gui.h | 3 +- core/rend/gui_achievements.cpp | 229 +++-- core/rend/gui_achievements.h | 13 +- core/rend/gui_util.cpp | 63 +- core/rend/gui_util.h | 90 +- core/rend/imgui_driver.cpp | 56 ++ core/rend/imgui_driver.h | 34 +- core/rend/mainui.cpp | 2 +- fonts/fa-solid-900.ttf.zip | Bin 0 -> 173936 bytes 18 files changed, 2207 insertions(+), 335 deletions(-) create mode 100644 core/rend/IconsFontAwesome6.h create mode 100644 core/rend/imgui_driver.cpp create mode 100644 fonts/fa-solid-900.ttf.zip diff --git a/CMakeLists.txt b/CMakeLists.txt index dd658157a..263d0b776 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -706,7 +706,7 @@ target_sources(${PROJECT_NAME} PRIVATE core/deps/lzma/7zArcIn.c core/deps/lzma/7 add_subdirectory(core/deps/libelf) target_link_libraries(${PROJECT_NAME} PRIVATE elf) if(NOT LIBRETRO) - target_compile_definitions(${PROJECT_NAME} PRIVATE IMGUI_DISABLE_DEMO_WINDOWS) + target_compile_definitions(${PROJECT_NAME} PRIVATE IMGUI_DISABLE_DEMO_WINDOWS IMGUI_DEFINE_MATH_OPERATORS) target_include_directories(${PROJECT_NAME} PRIVATE core/deps/imgui core/deps/imgui/backends) target_sources(${PROJECT_NAME} PRIVATE core/deps/imgui/imgui.cpp @@ -1015,7 +1015,8 @@ cmrc_add_resources(flycast-resources if(NOT LIBRETRO) cmrc_add_resources(flycast-resources fonts/Roboto-Medium.ttf.zip - fonts/Roboto-Regular.ttf.zip) + fonts/Roboto-Regular.ttf.zip + fonts/fa-solid-900.ttf.zip) if(ANDROID) cmrc_add_resources(flycast-resources WHENCE resources @@ -1292,6 +1293,7 @@ target_sources(${PROJECT_NAME} PRIVATE if(NOT LIBRETRO) target_sources(${PROJECT_NAME} PRIVATE core/rend/game_scanner.h + core/rend/imgui_driver.cpp core/rend/imgui_driver.h core/rend/gui.cpp core/rend/gui.h diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp index 6d544e184..19cb7781d 100644 --- a/core/achievements/achievements.cpp +++ b/core/achievements/achievements.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include namespace achievements @@ -51,6 +52,9 @@ public: std::future login(const char *username, const char *password); void logout(); bool isLoggedOn() const { return loggedOn; } + bool isActive() const { return active; } + Game getCurrentGame(); + std::vector getAchievementList(); void serialize(Serializer& ser); void deserialize(Deserializer& deser); @@ -66,6 +70,7 @@ private: void resumeGame(); void loadCache(); std::string getOrDownloadImage(const char *url); + std::tuple getCachedImage(const char *url); void diskChange(); static void clientLoginWithTokenCallback(int result, const char *error_message, rc_client_t *client, void *userdata); @@ -96,6 +101,8 @@ private: cResetEvent resetEvent; std::string cachePath; std::unordered_map cacheMap; + std::mutex cacheMutex; + std::future asyncImageDownload; }; bool init() @@ -123,6 +130,21 @@ bool isLoggedOn() return Achievements::Instance().isLoggedOn(); } +bool isActive() +{ + return Achievements::Instance().isActive(); +} + +Game getCurrentGame() +{ + return Achievements::Instance().getCurrentGame(); +} + +std::vector getAchievementList() +{ + return Achievements::Instance().getAchievementList(); +} + void serialize(Serializer& ser) { Achievements::Instance().serialize(ser); @@ -224,39 +246,63 @@ void Achievements::loadCache() continue; std::string s = get_file_basename(name); u64 v = strtoull(s.c_str(), nullptr, 16); + std::lock_guard _(cacheMutex); cacheMap[v] = name; } + closedir(dir); + } +} + +static u64 hashUrl(const char *url) { + return XXH64(url, strlen(url), 13); +} + +std::tuple Achievements::getCachedImage(const char *url) +{ + u64 hash = hashUrl(url); + std::lock_guard _(cacheMutex); + auto it = cacheMap.find(hash); + if (it != cacheMap.end()) { + return make_tuple(cachePath + it->second, true); + } + else + { + std::stringstream stream; + stream << std::hex << hash << ".png"; + return make_tuple(cachePath + stream.str(), false); } } std::string Achievements::getOrDownloadImage(const char *url) { - u64 hash = XXH64(url, strlen(url), 13); - auto it = cacheMap.find(hash); - if (it != cacheMap.end()) - return cachePath + it->second; + u64 hash = hashUrl(url); + { + std::lock_guard _(cacheMutex); + auto it = cacheMap.find(hash); + if (it != cacheMap.end()) + return cachePath + it->second; + } std::vector content; std::string content_type; int rc = http::get(url, content, content_type); if (!http::success(rc)) return {}; std::stringstream stream; - stream << std::hex << hash; - if (content_type == "image/jpeg") - stream << ".jpg"; - else - stream << ".png"; - std::string path = cachePath + stream.str(); - FILE *f = nowide::fopen(path.c_str(), "wb"); + stream << std::hex << hash << ".png"; + std::string localPath = cachePath + stream.str(); + FILE *f = nowide::fopen(localPath.c_str(), "wb"); if (f == nullptr) { - WARN_LOG(COMMON, "Can't save image to %s", path.c_str()); + WARN_LOG(COMMON, "Can't save image to %s", localPath.c_str()); return {}; } fwrite(content.data(), 1, content.size(), f); fclose(f); - cacheMap[hash] = stream.str(); - DEBUG_LOG(COMMON, "RA: downloaded %s to %s", url, path.c_str()); - return path; + { + std::lock_guard _(cacheMutex); + cacheMap[hash] = stream.str(); + } + DEBUG_LOG(COMMON, "RA: downloaded %s to %s", url, localPath.c_str()); + return localPath; } void Achievements::term() @@ -264,6 +310,8 @@ void Achievements::term() if (rc_client == nullptr) return; unloadGame(); + if (asyncImageDownload.valid()) + asyncImageDownload.get(); rc_client_destroy(rc_client); rc_client = nullptr; } @@ -507,15 +555,26 @@ void Achievements::handleUnlockEvent(const rc_client_event_t *event) void Achievements::handleAchievementChallengeIndicatorShowEvent(const rc_client_event_t *event) { - // TODO there might be more than one. Need to display an icon. - //std::string msg = "Challenge: " + std::string(event->achievement->title); - //gui_display_notification(msg.c_str(), 10000); INFO_LOG(COMMON, "RA: Challenge: %s", event->achievement->title); + char url[128]; + int rc = rc_client_achievement_get_image_url(event->achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED, url, sizeof(url)); + if (rc == RC_OK) + { + std::string image = getOrDownloadImage(url); + notifier.showChallenge(image); + } } void Achievements::handleAchievementChallengeIndicatorHideEvent(const rc_client_event_t *event) { - // TODO + INFO_LOG(COMMON, "RA: Challenge hidden: %s", event->achievement->title); + char url[128]; + int rc = rc_client_achievement_get_image_url(event->achievement, RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED, url, sizeof(url)); + if (rc == RC_OK) + { + std::string image = getOrDownloadImage(url); + notifier.hideChallenge(image); + } } void Achievements::handleGameCompleted(const rc_client_event_t *event) @@ -551,6 +610,7 @@ void Achievements::handleUpdateAchievementProgress(const rc_client_event_t *even notifier.notify(Notification::Progress, image, event->achievement->measured_progress); } +static Disc *hashDisk; static bool add150; static void *cdreader_open_track(const char* path, u32 track) @@ -558,54 +618,31 @@ static void *cdreader_open_track(const char* path, u32 track) DEBUG_LOG(COMMON, "RA: cdreader_open_track %s track %d", path, track); if (track == RC_HASH_CDTRACK_FIRST_DATA) { - u32 toc[102]; - libGDR_GetToc(toc, SingleDensity); - for (int i = 0; i < 99; i++) - if (toc[i] != 0xffffffff) - { - if (((toc[i] >> 4) & 0xf) & 4) - return reinterpret_cast(i + 1); - } - if (libGDR_GetDiscType() == GdRom) - { - libGDR_GetToc(toc, DoubleDensity); - for (int i = 0; i < 99; i++) - if (toc[i] != 0xffffffff) - { - if (((toc[i] >> 4) & 0xf) & 4) - return reinterpret_cast(i + 1); - } - } - } - u32 start, end; - if (!libGDR_GetTrack(track, start, end)) + for (const Track& track : hashDisk->tracks) + if (track.isDataTrack()) + return const_cast(&track); return nullptr; + } + if (track <= hashDisk->tracks.size()) + return const_cast(&hashDisk->tracks[track - 1]); else - return reinterpret_cast(track); + return nullptr; } static size_t cdreader_read_sector(void* track_handle, u32 sector, void* buffer, size_t requested_bytes) { - //DEBUG_LOG(COMMON, "RA: cdreader_read_sector track %zd sec %d num %zd", reinterpret_cast(track_handle), sector, requested_bytes); if (requested_bytes == 2048) // add 150 sectors to FAD corresponding to files + // FIXME get rid of this add150 = true; - if (add150) // FIXME how to get rid of this? + //DEBUG_LOG(COMMON, "RA: cdreader_read_sector track %p sec %d+%d num %zd", track_handle, sector, add150 ? 150 : 0, requested_bytes); + if (add150) sector += 150; - u32 count = requested_bytes; - u32 secNum = count / 2048; - if (secNum > 0) - { - libGDR_ReadSector((u8 *)buffer, sector, secNum, 2048); - buffer = (u8 *)buffer + secNum * 2048; - count -= secNum * 2048; - } - if (count > 0) - { - u8 locbuf[2048]; - libGDR_ReadSector(locbuf, sector + secNum, 1, 2048); - memcpy(buffer, locbuf, count); - } + u8 locbuf[2048]; + hashDisk->ReadSectors(sector, 1, locbuf, 2048); + requested_bytes = std::min(requested_bytes, 2048); + memcpy(buffer, locbuf, requested_bytes); + return requested_bytes; } @@ -615,12 +652,9 @@ static void cdreader_close_track(void* track_handle) static u32 cdreader_first_track_sector(void* track_handle) { - u32 trackNum = reinterpret_cast(track_handle); - u32 start, end; - if (!libGDR_GetTrack(trackNum, start, end)) - return 0; - DEBUG_LOG(COMMON, "RA: cdreader_first_track_sector track %d -> %d", trackNum, start); - return start; + Track& track = *static_cast(track_handle); + DEBUG_LOG(COMMON, "RA: cdreader_first_track_sector track %p -> %d", track_handle, track.StartFAD); + return track.StartFAD; } std::string Achievements::getGameHash() @@ -629,7 +663,13 @@ std::string Achievements::getGameHash() { const u32 diskType = libGDR_GetDiscType(); if (diskType == NoDisk || diskType == Open) - return ""; + return {}; + // Reopen the disk locally to avoid threading issues (CHD) + try { + hashDisk = OpenDisc(settings.content.path); + } catch (const FlycastException& e) { + return {}; + } add150 = false; rc_hash_cdreader hooks = { cdreader_open_track, @@ -641,7 +681,7 @@ std::string Achievements::getGameHash() rc_hash_init_error_message_callback([](const char *msg) { WARN_LOG(COMMON, "cdreader: %s", msg); }); -#ifndef NDEBUG +#if !defined(NDEBUG) || defined(DEBUGFAST) rc_hash_init_verbose_message_callback([](const char *msg) { DEBUG_LOG(COMMON, "cdreader: %s", msg); }); @@ -650,6 +690,8 @@ std::string Achievements::getGameHash() char hash[33] {}; rc_hash_generate_from_file(hash, settings.platform.isConsole() ? RC_CONSOLE_DREAMCAST : RC_CONSOLE_ARCADE, settings.content.fileName.c_str()); // fileName is only used for arcade + delete hashDisk; + hashDisk = nullptr; return hash; } @@ -724,7 +766,7 @@ void Achievements::loadGame() } if (!init() || !isLoggedOn()) { if (!isLoggedOn()) - WARN_LOG(COMMON, "Not logged on. Not loading game yet"); + INFO_LOG(COMMON, "Not logged on. Not loading game yet"); loadingGame = false; return; } @@ -735,6 +777,10 @@ void Achievements::loadGame() ((Achievements *)userdata)->gameLoaded(result, error_message); }, this); } + else { + INFO_LOG(COMMON, "RA: empty hash. Aborting load"); + loadingGame = false; + } } void Achievements::gameLoaded(int result, const char *errorMessage) @@ -811,6 +857,93 @@ void Achievements::diskChange() }, this); } +Game Achievements::getCurrentGame() +{ + if (!active) + return Game{}; + const rc_client_game_t *info = rc_client_get_game_info(rc_client); + if (info == nullptr) + return Game{}; + char url[128]; + std::string image; + if (rc_client_game_get_image_url(info, url, sizeof(url)) == RC_OK) + { + bool cached; + std::tie(image, cached) = getCachedImage(url); + if (!cached) + { + if (asyncImageDownload.valid()) + { + if (asyncImageDownload.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) + INFO_LOG(COMMON, "Async image download already in progress"); + else + asyncImageDownload.get(); + } + if (!asyncImageDownload.valid()) + { + std::string surl = url; + asyncImageDownload = std::async(std::launch::async, [this, surl]() { + getOrDownloadImage(surl.c_str()); + }); + } + } + } + rc_client_user_game_summary_t summary; + rc_client_get_user_game_summary(rc_client, &summary); + + return Game{ image, info->title, summary.num_unlocked_achievements, summary.num_core_achievements, summary.points_unlocked, summary.points_core }; +} + +std::vector Achievements::getAchievementList() +{ + std::vector achievements; + rc_client_achievement_list_t *list = rc_client_create_achievement_list(rc_client, + RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL, + RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS); + std::vector uncachedImages; + for (u32 i = 0; i < list->num_buckets; i++) + { + const char *label = list->buckets[i].label; + for (u32 j = 0; j < list->buckets[i].num_achievements; j++) + { + const rc_client_achievement_t *achievement = list->buckets[i].achievements[j]; + char url[128]; + std::string image; + if (rc_client_achievement_get_image_url(achievement, achievement->state, url, sizeof(url)) == RC_OK) + { + bool cached; + std::tie(image, cached) = getCachedImage(url); + if (!cached) + uncachedImages.push_back(url); + } + std::string status; + if (achievement->measured_percent) + status = achievement->measured_progress; + achievements.emplace_back(image, achievement->title, achievement->description, label, status); + } + } + rc_client_destroy_achievement_list(list); + if (!uncachedImages.empty()) + { + if (asyncImageDownload.valid()) + { + if (asyncImageDownload.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) + INFO_LOG(COMMON, "Async image download already in progress"); + else + asyncImageDownload.get(); + } + if (!asyncImageDownload.valid()) + { + asyncImageDownload = std::async(std::launch::async, [this, uncachedImages]() { + for (const std::string& url : uncachedImages) + getOrDownloadImage(url.c_str()); + }); + } + } + + return achievements; +} + void Achievements::serialize(Serializer& ser) { u32 size = (u32)rc_client_progress_size(rc_client); diff --git a/core/achievements/achievements.h b/core/achievements/achievements.h index 28023cc28..8b72df131 100644 --- a/core/achievements/achievements.h +++ b/core/achievements/achievements.h @@ -17,16 +17,42 @@ #pragma once #include "types.h" #include +#include namespace achievements { #ifdef USE_RACHIEVEMENTS +struct Game +{ + std::string image; + std::string title; + u32 unlockedAchievements; + u32 totalAchievements; + u32 points; + u32 totalPoints; +}; + +struct Achievement +{ + Achievement() = default; + Achievement(const std::string& image, const std::string& title, const std::string& description, const std::string& category, const std::string& status) + : image(image), title(title), description(description), category(category), status(status) {} + std::string image; + std::string title; + std::string description; + std::string category; + std::string status; +}; + bool init(); void term(); std::future login(const char *username, const char *password); void logout(); bool isLoggedOn(); +bool isActive(); +Game getCurrentGame(); +std::vector getAchievementList(); #endif diff --git a/core/emulator.h b/core/emulator.h index f4b8b14c0..f23835441 100644 --- a/core/emulator.h +++ b/core/emulator.h @@ -38,6 +38,7 @@ void dc_exit(); void dc_savestate(int index = 0); void dc_loadstate(int index = 0); void dc_loadstate(Deserializer& deser); +std::string dc_getStateUpdateDate(int index); enum class Event { Start, diff --git a/core/nullDC.cpp b/core/nullDC.cpp index ef0a5b8d3..da3379f29 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -7,6 +7,7 @@ #include "log/LogManager.h" #include "rend/gui.h" #include "oslib/oslib.h" +#include "oslib/directory.h" #include "debug/gdb_server.h" #include "archive/rzip.h" #include "rend/mainui.h" @@ -14,6 +15,7 @@ #include "lua/lua.h" #include "stdclass.h" #include "serialize.h" +#include int flycast_init(int argc, char* argv[]) { @@ -231,4 +233,32 @@ void dc_loadstate(int index) EventManager::event(Event::LoadState); } +#ifdef _WIN32 +static struct tm *localtime_r(const time_t *_clock, struct tm *_result) +{ + return localtime_s(_result, _clock) ? nullptr : _result; +} +#endif + +std::string dc_getStateUpdateDate(int index) +{ + std::string filename = hostfs::getSavestatePath(index, false); + struct stat st; + if (flycast::stat(filename.c_str(), &st) != 0) + return {}; + time_t ago = time(nullptr) - st.st_mtime; + if (ago < 60) + return std::to_string(ago) + " seconds ago"; + if (ago < 3600) + return std::to_string(ago / 60) + " minutes ago"; + if (ago < 3600 * 24) + return std::to_string(ago / 3600) + " hours ago"; + tm t; + if (localtime_r(&st.st_mtime, &t) == nullptr) + return {}; + std::string s(32, '\0'); + s.resize(snprintf(s.data(), 32, "%04d/%02d/%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)); + return s; +} + #endif diff --git a/core/oslib/storage.cpp b/core/oslib/storage.cpp index e3c7055c5..69e0ec259 100644 --- a/core/oslib/storage.cpp +++ b/core/oslib/storage.cpp @@ -184,7 +184,8 @@ public: #ifndef _WIN32 struct stat st; if (flycast::stat(path.c_str(), &st) != 0) { - INFO_LOG(COMMON, "Cannot stat file '%s' errno %d", path.c_str(), errno); + if (errno != ENOENT) + INFO_LOG(COMMON, "Cannot stat file '%s' errno %d", path.c_str(), errno); throw StorageException("Cannot stat " + path); } info.isDirectory = S_ISDIR(st.st_mode); diff --git a/core/rend/CustomTexture.cpp b/core/rend/CustomTexture.cpp index 6edcfb0ed..fd082cdd0 100644 --- a/core/rend/CustomTexture.cpp +++ b/core/rend/CustomTexture.cpp @@ -284,7 +284,7 @@ void CustomTexture::DumpTexture(u32 hash, int w, int h, TextureType textype, voi FILE *f = nowide::fopen((const char *)context, "wb"); if (f == nullptr) { - WARN_LOG(RENDERER, "Dump texture: can't save to file %s: error %d", context, errno); + WARN_LOG(RENDERER, "Dump texture: can't save to file %s: error %d", (const char *)context, errno); } else { diff --git a/core/rend/IconsFontAwesome6.h b/core/rend/IconsFontAwesome6.h new file mode 100644 index 000000000..a9e11503c --- /dev/null +++ b/core/rend/IconsFontAwesome6.h @@ -0,0 +1,1406 @@ +// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py +// for C and C++ +// from codepoints https://github.com/FortAwesome/Font-Awesome/raw/6.x/metadata/icons.yml +// for use with font https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-regular-400.ttf, https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-solid-900.ttf + +#pragma once + +#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf" +#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf" + +#define ICON_MIN_FA 0xe005 +#define ICON_MAX_16_FA 0xf8ff +#define ICON_MAX_FA 0xf8ff + +#define ICON_FA_0 "0" // U+0030 +#define ICON_FA_1 "1" // U+0031 +#define ICON_FA_2 "2" // U+0032 +#define ICON_FA_3 "3" // U+0033 +#define ICON_FA_4 "4" // U+0034 +#define ICON_FA_5 "5" // U+0035 +#define ICON_FA_6 "6" // U+0036 +#define ICON_FA_7 "7" // U+0037 +#define ICON_FA_8 "8" // U+0038 +#define ICON_FA_9 "9" // U+0039 +#define ICON_FA_A "A" // U+0041 +#define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9 +#define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb +#define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037 +#define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039 +#define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036 +#define ICON_FA_ALIGN_RIGHT "\xef\x80\xb8" // U+f038 +#define ICON_FA_ANCHOR "\xef\x84\xbd" // U+f13d +#define ICON_FA_ANCHOR_CIRCLE_CHECK "\xee\x92\xaa" // U+e4aa +#define ICON_FA_ANCHOR_CIRCLE_EXCLAMATION "\xee\x92\xab" // U+e4ab +#define ICON_FA_ANCHOR_CIRCLE_XMARK "\xee\x92\xac" // U+e4ac +#define ICON_FA_ANCHOR_LOCK "\xee\x92\xad" // U+e4ad +#define ICON_FA_ANGLE_DOWN "\xef\x84\x87" // U+f107 +#define ICON_FA_ANGLE_LEFT "\xef\x84\x84" // U+f104 +#define ICON_FA_ANGLE_RIGHT "\xef\x84\x85" // U+f105 +#define ICON_FA_ANGLE_UP "\xef\x84\x86" // U+f106 +#define ICON_FA_ANGLES_DOWN "\xef\x84\x83" // U+f103 +#define ICON_FA_ANGLES_LEFT "\xef\x84\x80" // U+f100 +#define ICON_FA_ANGLES_RIGHT "\xef\x84\x81" // U+f101 +#define ICON_FA_ANGLES_UP "\xef\x84\x82" // U+f102 +#define ICON_FA_ANKH "\xef\x99\x84" // U+f644 +#define ICON_FA_APPLE_WHOLE "\xef\x97\x91" // U+f5d1 +#define ICON_FA_ARCHWAY "\xef\x95\x97" // U+f557 +#define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063 +#define ICON_FA_ARROW_DOWN_1_9 "\xef\x85\xa2" // U+f162 +#define ICON_FA_ARROW_DOWN_9_1 "\xef\xa2\x86" // U+f886 +#define ICON_FA_ARROW_DOWN_A_Z "\xef\x85\x9d" // U+f15d +#define ICON_FA_ARROW_DOWN_LONG "\xef\x85\xb5" // U+f175 +#define ICON_FA_ARROW_DOWN_SHORT_WIDE "\xef\xa2\x84" // U+f884 +#define ICON_FA_ARROW_DOWN_UP_ACROSS_LINE "\xee\x92\xaf" // U+e4af +#define ICON_FA_ARROW_DOWN_UP_LOCK "\xee\x92\xb0" // U+e4b0 +#define ICON_FA_ARROW_DOWN_WIDE_SHORT "\xef\x85\xa0" // U+f160 +#define ICON_FA_ARROW_DOWN_Z_A "\xef\xa2\x81" // U+f881 +#define ICON_FA_ARROW_LEFT "\xef\x81\xa0" // U+f060 +#define ICON_FA_ARROW_LEFT_LONG "\xef\x85\xb7" // U+f177 +#define ICON_FA_ARROW_POINTER "\xef\x89\x85" // U+f245 +#define ICON_FA_ARROW_RIGHT "\xef\x81\xa1" // U+f061 +#define ICON_FA_ARROW_RIGHT_ARROW_LEFT "\xef\x83\xac" // U+f0ec +#define ICON_FA_ARROW_RIGHT_FROM_BRACKET "\xef\x82\x8b" // U+f08b +#define ICON_FA_ARROW_RIGHT_LONG "\xef\x85\xb8" // U+f178 +#define ICON_FA_ARROW_RIGHT_TO_BRACKET "\xef\x82\x90" // U+f090 +#define ICON_FA_ARROW_RIGHT_TO_CITY "\xee\x92\xb3" // U+e4b3 +#define ICON_FA_ARROW_ROTATE_LEFT "\xef\x83\xa2" // U+f0e2 +#define ICON_FA_ARROW_ROTATE_RIGHT "\xef\x80\x9e" // U+f01e +#define ICON_FA_ARROW_TREND_DOWN "\xee\x82\x97" // U+e097 +#define ICON_FA_ARROW_TREND_UP "\xee\x82\x98" // U+e098 +#define ICON_FA_ARROW_TURN_DOWN "\xef\x85\x89" // U+f149 +#define ICON_FA_ARROW_TURN_UP "\xef\x85\x88" // U+f148 +#define ICON_FA_ARROW_UP "\xef\x81\xa2" // U+f062 +#define ICON_FA_ARROW_UP_1_9 "\xef\x85\xa3" // U+f163 +#define ICON_FA_ARROW_UP_9_1 "\xef\xa2\x87" // U+f887 +#define ICON_FA_ARROW_UP_A_Z "\xef\x85\x9e" // U+f15e +#define ICON_FA_ARROW_UP_FROM_BRACKET "\xee\x82\x9a" // U+e09a +#define ICON_FA_ARROW_UP_FROM_GROUND_WATER "\xee\x92\xb5" // U+e4b5 +#define ICON_FA_ARROW_UP_FROM_WATER_PUMP "\xee\x92\xb6" // U+e4b6 +#define ICON_FA_ARROW_UP_LONG "\xef\x85\xb6" // U+f176 +#define ICON_FA_ARROW_UP_RIGHT_DOTS "\xee\x92\xb7" // U+e4b7 +#define ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE "\xef\x82\x8e" // U+f08e +#define ICON_FA_ARROW_UP_SHORT_WIDE "\xef\xa2\x85" // U+f885 +#define ICON_FA_ARROW_UP_WIDE_SHORT "\xef\x85\xa1" // U+f161 +#define ICON_FA_ARROW_UP_Z_A "\xef\xa2\x82" // U+f882 +#define ICON_FA_ARROWS_DOWN_TO_LINE "\xee\x92\xb8" // U+e4b8 +#define ICON_FA_ARROWS_DOWN_TO_PEOPLE "\xee\x92\xb9" // U+e4b9 +#define ICON_FA_ARROWS_LEFT_RIGHT "\xef\x81\xbe" // U+f07e +#define ICON_FA_ARROWS_LEFT_RIGHT_TO_LINE "\xee\x92\xba" // U+e4ba +#define ICON_FA_ARROWS_ROTATE "\xef\x80\xa1" // U+f021 +#define ICON_FA_ARROWS_SPIN "\xee\x92\xbb" // U+e4bb +#define ICON_FA_ARROWS_SPLIT_UP_AND_LEFT "\xee\x92\xbc" // U+e4bc +#define ICON_FA_ARROWS_TO_CIRCLE "\xee\x92\xbd" // U+e4bd +#define ICON_FA_ARROWS_TO_DOT "\xee\x92\xbe" // U+e4be +#define ICON_FA_ARROWS_TO_EYE "\xee\x92\xbf" // U+e4bf +#define ICON_FA_ARROWS_TURN_RIGHT "\xee\x93\x80" // U+e4c0 +#define ICON_FA_ARROWS_TURN_TO_DOTS "\xee\x93\x81" // U+e4c1 +#define ICON_FA_ARROWS_UP_DOWN "\xef\x81\xbd" // U+f07d +#define ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT "\xef\x81\x87" // U+f047 +#define ICON_FA_ARROWS_UP_TO_LINE "\xee\x93\x82" // U+e4c2 +#define ICON_FA_ASTERISK "*" // U+002a +#define ICON_FA_AT "@" // U+0040 +#define ICON_FA_ATOM "\xef\x97\x92" // U+f5d2 +#define ICON_FA_AUDIO_DESCRIPTION "\xef\x8a\x9e" // U+f29e +#define ICON_FA_AUSTRAL_SIGN "\xee\x82\xa9" // U+e0a9 +#define ICON_FA_AWARD "\xef\x95\x99" // U+f559 +#define ICON_FA_B "B" // U+0042 +#define ICON_FA_BABY "\xef\x9d\xbc" // U+f77c +#define ICON_FA_BABY_CARRIAGE "\xef\x9d\xbd" // U+f77d +#define ICON_FA_BACKWARD "\xef\x81\x8a" // U+f04a +#define ICON_FA_BACKWARD_FAST "\xef\x81\x89" // U+f049 +#define ICON_FA_BACKWARD_STEP "\xef\x81\x88" // U+f048 +#define ICON_FA_BACON "\xef\x9f\xa5" // U+f7e5 +#define ICON_FA_BACTERIA "\xee\x81\x99" // U+e059 +#define ICON_FA_BACTERIUM "\xee\x81\x9a" // U+e05a +#define ICON_FA_BAG_SHOPPING "\xef\x8a\x90" // U+f290 +#define ICON_FA_BAHAI "\xef\x99\xa6" // U+f666 +#define ICON_FA_BAHT_SIGN "\xee\x82\xac" // U+e0ac +#define ICON_FA_BAN "\xef\x81\x9e" // U+f05e +#define ICON_FA_BAN_SMOKING "\xef\x95\x8d" // U+f54d +#define ICON_FA_BANDAGE "\xef\x91\xa2" // U+f462 +#define ICON_FA_BANGLADESHI_TAKA_SIGN "\xee\x8b\xa6" // U+e2e6 +#define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a +#define ICON_FA_BARS "\xef\x83\x89" // U+f0c9 +#define ICON_FA_BARS_PROGRESS "\xef\xa0\xa8" // U+f828 +#define ICON_FA_BARS_STAGGERED "\xef\x95\x90" // U+f550 +#define ICON_FA_BASEBALL "\xef\x90\xb3" // U+f433 +#define ICON_FA_BASEBALL_BAT_BALL "\xef\x90\xb2" // U+f432 +#define ICON_FA_BASKET_SHOPPING "\xef\x8a\x91" // U+f291 +#define ICON_FA_BASKETBALL "\xef\x90\xb4" // U+f434 +#define ICON_FA_BATH "\xef\x8b\x8d" // U+f2cd +#define ICON_FA_BATTERY_EMPTY "\xef\x89\x84" // U+f244 +#define ICON_FA_BATTERY_FULL "\xef\x89\x80" // U+f240 +#define ICON_FA_BATTERY_HALF "\xef\x89\x82" // U+f242 +#define ICON_FA_BATTERY_QUARTER "\xef\x89\x83" // U+f243 +#define ICON_FA_BATTERY_THREE_QUARTERS "\xef\x89\x81" // U+f241 +#define ICON_FA_BED "\xef\x88\xb6" // U+f236 +#define ICON_FA_BED_PULSE "\xef\x92\x87" // U+f487 +#define ICON_FA_BEER_MUG_EMPTY "\xef\x83\xbc" // U+f0fc +#define ICON_FA_BELL "\xef\x83\xb3" // U+f0f3 +#define ICON_FA_BELL_CONCIERGE "\xef\x95\xa2" // U+f562 +#define ICON_FA_BELL_SLASH "\xef\x87\xb6" // U+f1f6 +#define ICON_FA_BEZIER_CURVE "\xef\x95\x9b" // U+f55b +#define ICON_FA_BICYCLE "\xef\x88\x86" // U+f206 +#define ICON_FA_BINOCULARS "\xef\x87\xa5" // U+f1e5 +#define ICON_FA_BIOHAZARD "\xef\x9e\x80" // U+f780 +#define ICON_FA_BITCOIN_SIGN "\xee\x82\xb4" // U+e0b4 +#define ICON_FA_BLENDER "\xef\x94\x97" // U+f517 +#define ICON_FA_BLENDER_PHONE "\xef\x9a\xb6" // U+f6b6 +#define ICON_FA_BLOG "\xef\x9e\x81" // U+f781 +#define ICON_FA_BOLD "\xef\x80\xb2" // U+f032 +#define ICON_FA_BOLT "\xef\x83\xa7" // U+f0e7 +#define ICON_FA_BOLT_LIGHTNING "\xee\x82\xb7" // U+e0b7 +#define ICON_FA_BOMB "\xef\x87\xa2" // U+f1e2 +#define ICON_FA_BONE "\xef\x97\x97" // U+f5d7 +#define ICON_FA_BONG "\xef\x95\x9c" // U+f55c +#define ICON_FA_BOOK "\xef\x80\xad" // U+f02d +#define ICON_FA_BOOK_ATLAS "\xef\x95\x98" // U+f558 +#define ICON_FA_BOOK_BIBLE "\xef\x99\x87" // U+f647 +#define ICON_FA_BOOK_BOOKMARK "\xee\x82\xbb" // U+e0bb +#define ICON_FA_BOOK_JOURNAL_WHILLS "\xef\x99\xaa" // U+f66a +#define ICON_FA_BOOK_MEDICAL "\xef\x9f\xa6" // U+f7e6 +#define ICON_FA_BOOK_OPEN "\xef\x94\x98" // U+f518 +#define ICON_FA_BOOK_OPEN_READER "\xef\x97\x9a" // U+f5da +#define ICON_FA_BOOK_QURAN "\xef\x9a\x87" // U+f687 +#define ICON_FA_BOOK_SKULL "\xef\x9a\xb7" // U+f6b7 +#define ICON_FA_BOOK_TANAKH "\xef\xa0\xa7" // U+f827 +#define ICON_FA_BOOKMARK "\xef\x80\xae" // U+f02e +#define ICON_FA_BORDER_ALL "\xef\xa1\x8c" // U+f84c +#define ICON_FA_BORDER_NONE "\xef\xa1\x90" // U+f850 +#define ICON_FA_BORDER_TOP_LEFT "\xef\xa1\x93" // U+f853 +#define ICON_FA_BORE_HOLE "\xee\x93\x83" // U+e4c3 +#define ICON_FA_BOTTLE_DROPLET "\xee\x93\x84" // U+e4c4 +#define ICON_FA_BOTTLE_WATER "\xee\x93\x85" // U+e4c5 +#define ICON_FA_BOWL_FOOD "\xee\x93\x86" // U+e4c6 +#define ICON_FA_BOWL_RICE "\xee\x8b\xab" // U+e2eb +#define ICON_FA_BOWLING_BALL "\xef\x90\xb6" // U+f436 +#define ICON_FA_BOX "\xef\x91\xa6" // U+f466 +#define ICON_FA_BOX_ARCHIVE "\xef\x86\x87" // U+f187 +#define ICON_FA_BOX_OPEN "\xef\x92\x9e" // U+f49e +#define ICON_FA_BOX_TISSUE "\xee\x81\x9b" // U+e05b +#define ICON_FA_BOXES_PACKING "\xee\x93\x87" // U+e4c7 +#define ICON_FA_BOXES_STACKED "\xef\x91\xa8" // U+f468 +#define ICON_FA_BRAILLE "\xef\x8a\xa1" // U+f2a1 +#define ICON_FA_BRAIN "\xef\x97\x9c" // U+f5dc +#define ICON_FA_BRAZILIAN_REAL_SIGN "\xee\x91\xac" // U+e46c +#define ICON_FA_BREAD_SLICE "\xef\x9f\xac" // U+f7ec +#define ICON_FA_BRIDGE "\xee\x93\x88" // U+e4c8 +#define ICON_FA_BRIDGE_CIRCLE_CHECK "\xee\x93\x89" // U+e4c9 +#define ICON_FA_BRIDGE_CIRCLE_EXCLAMATION "\xee\x93\x8a" // U+e4ca +#define ICON_FA_BRIDGE_CIRCLE_XMARK "\xee\x93\x8b" // U+e4cb +#define ICON_FA_BRIDGE_LOCK "\xee\x93\x8c" // U+e4cc +#define ICON_FA_BRIDGE_WATER "\xee\x93\x8e" // U+e4ce +#define ICON_FA_BRIEFCASE "\xef\x82\xb1" // U+f0b1 +#define ICON_FA_BRIEFCASE_MEDICAL "\xef\x91\xa9" // U+f469 +#define ICON_FA_BROOM "\xef\x94\x9a" // U+f51a +#define ICON_FA_BROOM_BALL "\xef\x91\x98" // U+f458 +#define ICON_FA_BRUSH "\xef\x95\x9d" // U+f55d +#define ICON_FA_BUCKET "\xee\x93\x8f" // U+e4cf +#define ICON_FA_BUG "\xef\x86\x88" // U+f188 +#define ICON_FA_BUG_SLASH "\xee\x92\x90" // U+e490 +#define ICON_FA_BUGS "\xee\x93\x90" // U+e4d0 +#define ICON_FA_BUILDING "\xef\x86\xad" // U+f1ad +#define ICON_FA_BUILDING_CIRCLE_ARROW_RIGHT "\xee\x93\x91" // U+e4d1 +#define ICON_FA_BUILDING_CIRCLE_CHECK "\xee\x93\x92" // U+e4d2 +#define ICON_FA_BUILDING_CIRCLE_EXCLAMATION "\xee\x93\x93" // U+e4d3 +#define ICON_FA_BUILDING_CIRCLE_XMARK "\xee\x93\x94" // U+e4d4 +#define ICON_FA_BUILDING_COLUMNS "\xef\x86\x9c" // U+f19c +#define ICON_FA_BUILDING_FLAG "\xee\x93\x95" // U+e4d5 +#define ICON_FA_BUILDING_LOCK "\xee\x93\x96" // U+e4d6 +#define ICON_FA_BUILDING_NGO "\xee\x93\x97" // U+e4d7 +#define ICON_FA_BUILDING_SHIELD "\xee\x93\x98" // U+e4d8 +#define ICON_FA_BUILDING_UN "\xee\x93\x99" // U+e4d9 +#define ICON_FA_BUILDING_USER "\xee\x93\x9a" // U+e4da +#define ICON_FA_BUILDING_WHEAT "\xee\x93\x9b" // U+e4db +#define ICON_FA_BULLHORN "\xef\x82\xa1" // U+f0a1 +#define ICON_FA_BULLSEYE "\xef\x85\x80" // U+f140 +#define ICON_FA_BURGER "\xef\xa0\x85" // U+f805 +#define ICON_FA_BURST "\xee\x93\x9c" // U+e4dc +#define ICON_FA_BUS "\xef\x88\x87" // U+f207 +#define ICON_FA_BUS_SIMPLE "\xef\x95\x9e" // U+f55e +#define ICON_FA_BUSINESS_TIME "\xef\x99\x8a" // U+f64a +#define ICON_FA_C "C" // U+0043 +#define ICON_FA_CABLE_CAR "\xef\x9f\x9a" // U+f7da +#define ICON_FA_CAKE_CANDLES "\xef\x87\xbd" // U+f1fd +#define ICON_FA_CALCULATOR "\xef\x87\xac" // U+f1ec +#define ICON_FA_CALENDAR "\xef\x84\xb3" // U+f133 +#define ICON_FA_CALENDAR_CHECK "\xef\x89\xb4" // U+f274 +#define ICON_FA_CALENDAR_DAY "\xef\x9e\x83" // U+f783 +#define ICON_FA_CALENDAR_DAYS "\xef\x81\xb3" // U+f073 +#define ICON_FA_CALENDAR_MINUS "\xef\x89\xb2" // U+f272 +#define ICON_FA_CALENDAR_PLUS "\xef\x89\xb1" // U+f271 +#define ICON_FA_CALENDAR_WEEK "\xef\x9e\x84" // U+f784 +#define ICON_FA_CALENDAR_XMARK "\xef\x89\xb3" // U+f273 +#define ICON_FA_CAMERA "\xef\x80\xb0" // U+f030 +#define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083 +#define ICON_FA_CAMERA_ROTATE "\xee\x83\x98" // U+e0d8 +#define ICON_FA_CAMPGROUND "\xef\x9a\xbb" // U+f6bb +#define ICON_FA_CANDY_CANE "\xef\x9e\x86" // U+f786 +#define ICON_FA_CANNABIS "\xef\x95\x9f" // U+f55f +#define ICON_FA_CAPSULES "\xef\x91\xab" // U+f46b +#define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9 +#define ICON_FA_CAR_BATTERY "\xef\x97\x9f" // U+f5df +#define ICON_FA_CAR_BURST "\xef\x97\xa1" // U+f5e1 +#define ICON_FA_CAR_ON "\xee\x93\x9d" // U+e4dd +#define ICON_FA_CAR_REAR "\xef\x97\x9e" // U+f5de +#define ICON_FA_CAR_SIDE "\xef\x97\xa4" // U+f5e4 +#define ICON_FA_CAR_TUNNEL "\xee\x93\x9e" // U+e4de +#define ICON_FA_CARAVAN "\xef\xa3\xbf" // U+f8ff +#define ICON_FA_CARET_DOWN "\xef\x83\x97" // U+f0d7 +#define ICON_FA_CARET_LEFT "\xef\x83\x99" // U+f0d9 +#define ICON_FA_CARET_RIGHT "\xef\x83\x9a" // U+f0da +#define ICON_FA_CARET_UP "\xef\x83\x98" // U+f0d8 +#define ICON_FA_CARROT "\xef\x9e\x87" // U+f787 +#define ICON_FA_CART_ARROW_DOWN "\xef\x88\x98" // U+f218 +#define ICON_FA_CART_FLATBED "\xef\x91\xb4" // U+f474 +#define ICON_FA_CART_FLATBED_SUITCASE "\xef\x96\x9d" // U+f59d +#define ICON_FA_CART_PLUS "\xef\x88\x97" // U+f217 +#define ICON_FA_CART_SHOPPING "\xef\x81\xba" // U+f07a +#define ICON_FA_CASH_REGISTER "\xef\x9e\x88" // U+f788 +#define ICON_FA_CAT "\xef\x9a\xbe" // U+f6be +#define ICON_FA_CEDI_SIGN "\xee\x83\x9f" // U+e0df +#define ICON_FA_CENT_SIGN "\xee\x8f\xb5" // U+e3f5 +#define ICON_FA_CERTIFICATE "\xef\x82\xa3" // U+f0a3 +#define ICON_FA_CHAIR "\xef\x9b\x80" // U+f6c0 +#define ICON_FA_CHALKBOARD "\xef\x94\x9b" // U+f51b +#define ICON_FA_CHALKBOARD_USER "\xef\x94\x9c" // U+f51c +#define ICON_FA_CHAMPAGNE_GLASSES "\xef\x9e\x9f" // U+f79f +#define ICON_FA_CHARGING_STATION "\xef\x97\xa7" // U+f5e7 +#define ICON_FA_CHART_AREA "\xef\x87\xbe" // U+f1fe +#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+f080 +#define ICON_FA_CHART_COLUMN "\xee\x83\xa3" // U+e0e3 +#define ICON_FA_CHART_GANTT "\xee\x83\xa4" // U+e0e4 +#define ICON_FA_CHART_LINE "\xef\x88\x81" // U+f201 +#define ICON_FA_CHART_PIE "\xef\x88\x80" // U+f200 +#define ICON_FA_CHART_SIMPLE "\xee\x91\xb3" // U+e473 +#define ICON_FA_CHECK "\xef\x80\x8c" // U+f00c +#define ICON_FA_CHECK_DOUBLE "\xef\x95\xa0" // U+f560 +#define ICON_FA_CHECK_TO_SLOT "\xef\x9d\xb2" // U+f772 +#define ICON_FA_CHEESE "\xef\x9f\xaf" // U+f7ef +#define ICON_FA_CHESS "\xef\x90\xb9" // U+f439 +#define ICON_FA_CHESS_BISHOP "\xef\x90\xba" // U+f43a +#define ICON_FA_CHESS_BOARD "\xef\x90\xbc" // U+f43c +#define ICON_FA_CHESS_KING "\xef\x90\xbf" // U+f43f +#define ICON_FA_CHESS_KNIGHT "\xef\x91\x81" // U+f441 +#define ICON_FA_CHESS_PAWN "\xef\x91\x83" // U+f443 +#define ICON_FA_CHESS_QUEEN "\xef\x91\x85" // U+f445 +#define ICON_FA_CHESS_ROOK "\xef\x91\x87" // U+f447 +#define ICON_FA_CHEVRON_DOWN "\xef\x81\xb8" // U+f078 +#define ICON_FA_CHEVRON_LEFT "\xef\x81\x93" // U+f053 +#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054 +#define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077 +#define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae +#define ICON_FA_CHILD_COMBATANT "\xee\x93\xa0" // U+e4e0 +#define ICON_FA_CHILD_DRESS "\xee\x96\x9c" // U+e59c +#define ICON_FA_CHILD_REACHING "\xee\x96\x9d" // U+e59d +#define ICON_FA_CHILDREN "\xee\x93\xa1" // U+e4e1 +#define ICON_FA_CHURCH "\xef\x94\x9d" // U+f51d +#define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111 +#define ICON_FA_CIRCLE_ARROW_DOWN "\xef\x82\xab" // U+f0ab +#define ICON_FA_CIRCLE_ARROW_LEFT "\xef\x82\xa8" // U+f0a8 +#define ICON_FA_CIRCLE_ARROW_RIGHT "\xef\x82\xa9" // U+f0a9 +#define ICON_FA_CIRCLE_ARROW_UP "\xef\x82\xaa" // U+f0aa +#define ICON_FA_CIRCLE_CHECK "\xef\x81\x98" // U+f058 +#define ICON_FA_CIRCLE_CHEVRON_DOWN "\xef\x84\xba" // U+f13a +#define ICON_FA_CIRCLE_CHEVRON_LEFT "\xef\x84\xb7" // U+f137 +#define ICON_FA_CIRCLE_CHEVRON_RIGHT "\xef\x84\xb8" // U+f138 +#define ICON_FA_CIRCLE_CHEVRON_UP "\xef\x84\xb9" // U+f139 +#define ICON_FA_CIRCLE_DOLLAR_TO_SLOT "\xef\x92\xb9" // U+f4b9 +#define ICON_FA_CIRCLE_DOT "\xef\x86\x92" // U+f192 +#define ICON_FA_CIRCLE_DOWN "\xef\x8d\x98" // U+f358 +#define ICON_FA_CIRCLE_EXCLAMATION "\xef\x81\xaa" // U+f06a +#define ICON_FA_CIRCLE_H "\xef\x91\xbe" // U+f47e +#define ICON_FA_CIRCLE_HALF_STROKE "\xef\x81\x82" // U+f042 +#define ICON_FA_CIRCLE_INFO "\xef\x81\x9a" // U+f05a +#define ICON_FA_CIRCLE_LEFT "\xef\x8d\x99" // U+f359 +#define ICON_FA_CIRCLE_MINUS "\xef\x81\x96" // U+f056 +#define ICON_FA_CIRCLE_NODES "\xee\x93\xa2" // U+e4e2 +#define ICON_FA_CIRCLE_NOTCH "\xef\x87\x8e" // U+f1ce +#define ICON_FA_CIRCLE_PAUSE "\xef\x8a\x8b" // U+f28b +#define ICON_FA_CIRCLE_PLAY "\xef\x85\x84" // U+f144 +#define ICON_FA_CIRCLE_PLUS "\xef\x81\x95" // U+f055 +#define ICON_FA_CIRCLE_QUESTION "\xef\x81\x99" // U+f059 +#define ICON_FA_CIRCLE_RADIATION "\xef\x9e\xba" // U+f7ba +#define ICON_FA_CIRCLE_RIGHT "\xef\x8d\x9a" // U+f35a +#define ICON_FA_CIRCLE_STOP "\xef\x8a\x8d" // U+f28d +#define ICON_FA_CIRCLE_UP "\xef\x8d\x9b" // U+f35b +#define ICON_FA_CIRCLE_USER "\xef\x8a\xbd" // U+f2bd +#define ICON_FA_CIRCLE_XMARK "\xef\x81\x97" // U+f057 +#define ICON_FA_CITY "\xef\x99\x8f" // U+f64f +#define ICON_FA_CLAPPERBOARD "\xee\x84\xb1" // U+e131 +#define ICON_FA_CLIPBOARD "\xef\x8c\xa8" // U+f328 +#define ICON_FA_CLIPBOARD_CHECK "\xef\x91\xac" // U+f46c +#define ICON_FA_CLIPBOARD_LIST "\xef\x91\xad" // U+f46d +#define ICON_FA_CLIPBOARD_QUESTION "\xee\x93\xa3" // U+e4e3 +#define ICON_FA_CLIPBOARD_USER "\xef\x9f\xb3" // U+f7f3 +#define ICON_FA_CLOCK "\xef\x80\x97" // U+f017 +#define ICON_FA_CLOCK_ROTATE_LEFT "\xef\x87\x9a" // U+f1da +#define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d +#define ICON_FA_CLOSED_CAPTIONING "\xef\x88\x8a" // U+f20a +#define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2 +#define ICON_FA_CLOUD_ARROW_DOWN "\xef\x83\xad" // U+f0ed +#define ICON_FA_CLOUD_ARROW_UP "\xef\x83\xae" // U+f0ee +#define ICON_FA_CLOUD_BOLT "\xef\x9d\xac" // U+f76c +#define ICON_FA_CLOUD_MEATBALL "\xef\x9c\xbb" // U+f73b +#define ICON_FA_CLOUD_MOON "\xef\x9b\x83" // U+f6c3 +#define ICON_FA_CLOUD_MOON_RAIN "\xef\x9c\xbc" // U+f73c +#define ICON_FA_CLOUD_RAIN "\xef\x9c\xbd" // U+f73d +#define ICON_FA_CLOUD_SHOWERS_HEAVY "\xef\x9d\x80" // U+f740 +#define ICON_FA_CLOUD_SHOWERS_WATER "\xee\x93\xa4" // U+e4e4 +#define ICON_FA_CLOUD_SUN "\xef\x9b\x84" // U+f6c4 +#define ICON_FA_CLOUD_SUN_RAIN "\xef\x9d\x83" // U+f743 +#define ICON_FA_CLOVER "\xee\x84\xb9" // U+e139 +#define ICON_FA_CODE "\xef\x84\xa1" // U+f121 +#define ICON_FA_CODE_BRANCH "\xef\x84\xa6" // U+f126 +#define ICON_FA_CODE_COMMIT "\xef\x8e\x86" // U+f386 +#define ICON_FA_CODE_COMPARE "\xee\x84\xba" // U+e13a +#define ICON_FA_CODE_FORK "\xee\x84\xbb" // U+e13b +#define ICON_FA_CODE_MERGE "\xef\x8e\x87" // U+f387 +#define ICON_FA_CODE_PULL_REQUEST "\xee\x84\xbc" // U+e13c +#define ICON_FA_COINS "\xef\x94\x9e" // U+f51e +#define ICON_FA_COLON_SIGN "\xee\x85\x80" // U+e140 +#define ICON_FA_COMMENT "\xef\x81\xb5" // U+f075 +#define ICON_FA_COMMENT_DOLLAR "\xef\x99\x91" // U+f651 +#define ICON_FA_COMMENT_DOTS "\xef\x92\xad" // U+f4ad +#define ICON_FA_COMMENT_MEDICAL "\xef\x9f\xb5" // U+f7f5 +#define ICON_FA_COMMENT_SLASH "\xef\x92\xb3" // U+f4b3 +#define ICON_FA_COMMENT_SMS "\xef\x9f\x8d" // U+f7cd +#define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086 +#define ICON_FA_COMMENTS_DOLLAR "\xef\x99\x93" // U+f653 +#define ICON_FA_COMPACT_DISC "\xef\x94\x9f" // U+f51f +#define ICON_FA_COMPASS "\xef\x85\x8e" // U+f14e +#define ICON_FA_COMPASS_DRAFTING "\xef\x95\xa8" // U+f568 +#define ICON_FA_COMPRESS "\xef\x81\xa6" // U+f066 +#define ICON_FA_COMPUTER "\xee\x93\xa5" // U+e4e5 +#define ICON_FA_COMPUTER_MOUSE "\xef\xa3\x8c" // U+f8cc +#define ICON_FA_COOKIE "\xef\x95\xa3" // U+f563 +#define ICON_FA_COOKIE_BITE "\xef\x95\xa4" // U+f564 +#define ICON_FA_COPY "\xef\x83\x85" // U+f0c5 +#define ICON_FA_COPYRIGHT "\xef\x87\xb9" // U+f1f9 +#define ICON_FA_COUCH "\xef\x92\xb8" // U+f4b8 +#define ICON_FA_COW "\xef\x9b\x88" // U+f6c8 +#define ICON_FA_CREDIT_CARD "\xef\x82\x9d" // U+f09d +#define ICON_FA_CROP "\xef\x84\xa5" // U+f125 +#define ICON_FA_CROP_SIMPLE "\xef\x95\xa5" // U+f565 +#define ICON_FA_CROSS "\xef\x99\x94" // U+f654 +#define ICON_FA_CROSSHAIRS "\xef\x81\x9b" // U+f05b +#define ICON_FA_CROW "\xef\x94\xa0" // U+f520 +#define ICON_FA_CROWN "\xef\x94\xa1" // U+f521 +#define ICON_FA_CRUTCH "\xef\x9f\xb7" // U+f7f7 +#define ICON_FA_CRUZEIRO_SIGN "\xee\x85\x92" // U+e152 +#define ICON_FA_CUBE "\xef\x86\xb2" // U+f1b2 +#define ICON_FA_CUBES "\xef\x86\xb3" // U+f1b3 +#define ICON_FA_CUBES_STACKED "\xee\x93\xa6" // U+e4e6 +#define ICON_FA_D "D" // U+0044 +#define ICON_FA_DATABASE "\xef\x87\x80" // U+f1c0 +#define ICON_FA_DELETE_LEFT "\xef\x95\x9a" // U+f55a +#define ICON_FA_DEMOCRAT "\xef\x9d\x87" // U+f747 +#define ICON_FA_DESKTOP "\xef\x8e\x90" // U+f390 +#define ICON_FA_DHARMACHAKRA "\xef\x99\x95" // U+f655 +#define ICON_FA_DIAGRAM_NEXT "\xee\x91\xb6" // U+e476 +#define ICON_FA_DIAGRAM_PREDECESSOR "\xee\x91\xb7" // U+e477 +#define ICON_FA_DIAGRAM_PROJECT "\xef\x95\x82" // U+f542 +#define ICON_FA_DIAGRAM_SUCCESSOR "\xee\x91\xba" // U+e47a +#define ICON_FA_DIAMOND "\xef\x88\x99" // U+f219 +#define ICON_FA_DIAMOND_TURN_RIGHT "\xef\x97\xab" // U+f5eb +#define ICON_FA_DICE "\xef\x94\xa2" // U+f522 +#define ICON_FA_DICE_D20 "\xef\x9b\x8f" // U+f6cf +#define ICON_FA_DICE_D6 "\xef\x9b\x91" // U+f6d1 +#define ICON_FA_DICE_FIVE "\xef\x94\xa3" // U+f523 +#define ICON_FA_DICE_FOUR "\xef\x94\xa4" // U+f524 +#define ICON_FA_DICE_ONE "\xef\x94\xa5" // U+f525 +#define ICON_FA_DICE_SIX "\xef\x94\xa6" // U+f526 +#define ICON_FA_DICE_THREE "\xef\x94\xa7" // U+f527 +#define ICON_FA_DICE_TWO "\xef\x94\xa8" // U+f528 +#define ICON_FA_DISEASE "\xef\x9f\xba" // U+f7fa +#define ICON_FA_DISPLAY "\xee\x85\xa3" // U+e163 +#define ICON_FA_DIVIDE "\xef\x94\xa9" // U+f529 +#define ICON_FA_DNA "\xef\x91\xb1" // U+f471 +#define ICON_FA_DOG "\xef\x9b\x93" // U+f6d3 +#define ICON_FA_DOLLAR_SIGN "$" // U+0024 +#define ICON_FA_DOLLY "\xef\x91\xb2" // U+f472 +#define ICON_FA_DONG_SIGN "\xee\x85\xa9" // U+e169 +#define ICON_FA_DOOR_CLOSED "\xef\x94\xaa" // U+f52a +#define ICON_FA_DOOR_OPEN "\xef\x94\xab" // U+f52b +#define ICON_FA_DOVE "\xef\x92\xba" // U+f4ba +#define ICON_FA_DOWN_LEFT_AND_UP_RIGHT_TO_CENTER "\xef\x90\xa2" // U+f422 +#define ICON_FA_DOWN_LONG "\xef\x8c\x89" // U+f309 +#define ICON_FA_DOWNLOAD "\xef\x80\x99" // U+f019 +#define ICON_FA_DRAGON "\xef\x9b\x95" // U+f6d5 +#define ICON_FA_DRAW_POLYGON "\xef\x97\xae" // U+f5ee +#define ICON_FA_DROPLET "\xef\x81\x83" // U+f043 +#define ICON_FA_DROPLET_SLASH "\xef\x97\x87" // U+f5c7 +#define ICON_FA_DRUM "\xef\x95\xa9" // U+f569 +#define ICON_FA_DRUM_STEELPAN "\xef\x95\xaa" // U+f56a +#define ICON_FA_DRUMSTICK_BITE "\xef\x9b\x97" // U+f6d7 +#define ICON_FA_DUMBBELL "\xef\x91\x8b" // U+f44b +#define ICON_FA_DUMPSTER "\xef\x9e\x93" // U+f793 +#define ICON_FA_DUMPSTER_FIRE "\xef\x9e\x94" // U+f794 +#define ICON_FA_DUNGEON "\xef\x9b\x99" // U+f6d9 +#define ICON_FA_E "E" // U+0045 +#define ICON_FA_EAR_DEAF "\xef\x8a\xa4" // U+f2a4 +#define ICON_FA_EAR_LISTEN "\xef\x8a\xa2" // U+f2a2 +#define ICON_FA_EARTH_AFRICA "\xef\x95\xbc" // U+f57c +#define ICON_FA_EARTH_AMERICAS "\xef\x95\xbd" // U+f57d +#define ICON_FA_EARTH_ASIA "\xef\x95\xbe" // U+f57e +#define ICON_FA_EARTH_EUROPE "\xef\x9e\xa2" // U+f7a2 +#define ICON_FA_EARTH_OCEANIA "\xee\x91\xbb" // U+e47b +#define ICON_FA_EGG "\xef\x9f\xbb" // U+f7fb +#define ICON_FA_EJECT "\xef\x81\x92" // U+f052 +#define ICON_FA_ELEVATOR "\xee\x85\xad" // U+e16d +#define ICON_FA_ELLIPSIS "\xef\x85\x81" // U+f141 +#define ICON_FA_ELLIPSIS_VERTICAL "\xef\x85\x82" // U+f142 +#define ICON_FA_ENVELOPE "\xef\x83\xa0" // U+f0e0 +#define ICON_FA_ENVELOPE_CIRCLE_CHECK "\xee\x93\xa8" // U+e4e8 +#define ICON_FA_ENVELOPE_OPEN "\xef\x8a\xb6" // U+f2b6 +#define ICON_FA_ENVELOPE_OPEN_TEXT "\xef\x99\x98" // U+f658 +#define ICON_FA_ENVELOPES_BULK "\xef\x99\xb4" // U+f674 +#define ICON_FA_EQUALS "=" // U+003d +#define ICON_FA_ERASER "\xef\x84\xad" // U+f12d +#define ICON_FA_ETHERNET "\xef\x9e\x96" // U+f796 +#define ICON_FA_EURO_SIGN "\xef\x85\x93" // U+f153 +#define ICON_FA_EXCLAMATION "!" // U+0021 +#define ICON_FA_EXPAND "\xef\x81\xa5" // U+f065 +#define ICON_FA_EXPLOSION "\xee\x93\xa9" // U+e4e9 +#define ICON_FA_EYE "\xef\x81\xae" // U+f06e +#define ICON_FA_EYE_DROPPER "\xef\x87\xbb" // U+f1fb +#define ICON_FA_EYE_LOW_VISION "\xef\x8a\xa8" // U+f2a8 +#define ICON_FA_EYE_SLASH "\xef\x81\xb0" // U+f070 +#define ICON_FA_F "F" // U+0046 +#define ICON_FA_FACE_ANGRY "\xef\x95\x96" // U+f556 +#define ICON_FA_FACE_DIZZY "\xef\x95\xa7" // U+f567 +#define ICON_FA_FACE_FLUSHED "\xef\x95\xb9" // U+f579 +#define ICON_FA_FACE_FROWN "\xef\x84\x99" // U+f119 +#define ICON_FA_FACE_FROWN_OPEN "\xef\x95\xba" // U+f57a +#define ICON_FA_FACE_GRIMACE "\xef\x95\xbf" // U+f57f +#define ICON_FA_FACE_GRIN "\xef\x96\x80" // U+f580 +#define ICON_FA_FACE_GRIN_BEAM "\xef\x96\x82" // U+f582 +#define ICON_FA_FACE_GRIN_BEAM_SWEAT "\xef\x96\x83" // U+f583 +#define ICON_FA_FACE_GRIN_HEARTS "\xef\x96\x84" // U+f584 +#define ICON_FA_FACE_GRIN_SQUINT "\xef\x96\x85" // U+f585 +#define ICON_FA_FACE_GRIN_SQUINT_TEARS "\xef\x96\x86" // U+f586 +#define ICON_FA_FACE_GRIN_STARS "\xef\x96\x87" // U+f587 +#define ICON_FA_FACE_GRIN_TEARS "\xef\x96\x88" // U+f588 +#define ICON_FA_FACE_GRIN_TONGUE "\xef\x96\x89" // U+f589 +#define ICON_FA_FACE_GRIN_TONGUE_SQUINT "\xef\x96\x8a" // U+f58a +#define ICON_FA_FACE_GRIN_TONGUE_WINK "\xef\x96\x8b" // U+f58b +#define ICON_FA_FACE_GRIN_WIDE "\xef\x96\x81" // U+f581 +#define ICON_FA_FACE_GRIN_WINK "\xef\x96\x8c" // U+f58c +#define ICON_FA_FACE_KISS "\xef\x96\x96" // U+f596 +#define ICON_FA_FACE_KISS_BEAM "\xef\x96\x97" // U+f597 +#define ICON_FA_FACE_KISS_WINK_HEART "\xef\x96\x98" // U+f598 +#define ICON_FA_FACE_LAUGH "\xef\x96\x99" // U+f599 +#define ICON_FA_FACE_LAUGH_BEAM "\xef\x96\x9a" // U+f59a +#define ICON_FA_FACE_LAUGH_SQUINT "\xef\x96\x9b" // U+f59b +#define ICON_FA_FACE_LAUGH_WINK "\xef\x96\x9c" // U+f59c +#define ICON_FA_FACE_MEH "\xef\x84\x9a" // U+f11a +#define ICON_FA_FACE_MEH_BLANK "\xef\x96\xa4" // U+f5a4 +#define ICON_FA_FACE_ROLLING_EYES "\xef\x96\xa5" // U+f5a5 +#define ICON_FA_FACE_SAD_CRY "\xef\x96\xb3" // U+f5b3 +#define ICON_FA_FACE_SAD_TEAR "\xef\x96\xb4" // U+f5b4 +#define ICON_FA_FACE_SMILE "\xef\x84\x98" // U+f118 +#define ICON_FA_FACE_SMILE_BEAM "\xef\x96\xb8" // U+f5b8 +#define ICON_FA_FACE_SMILE_WINK "\xef\x93\x9a" // U+f4da +#define ICON_FA_FACE_SURPRISE "\xef\x97\x82" // U+f5c2 +#define ICON_FA_FACE_TIRED "\xef\x97\x88" // U+f5c8 +#define ICON_FA_FAN "\xef\xa1\xa3" // U+f863 +#define ICON_FA_FAUCET "\xee\x80\x85" // U+e005 +#define ICON_FA_FAUCET_DRIP "\xee\x80\x86" // U+e006 +#define ICON_FA_FAX "\xef\x86\xac" // U+f1ac +#define ICON_FA_FEATHER "\xef\x94\xad" // U+f52d +#define ICON_FA_FEATHER_POINTED "\xef\x95\xab" // U+f56b +#define ICON_FA_FERRY "\xee\x93\xaa" // U+e4ea +#define ICON_FA_FILE "\xef\x85\x9b" // U+f15b +#define ICON_FA_FILE_ARROW_DOWN "\xef\x95\xad" // U+f56d +#define ICON_FA_FILE_ARROW_UP "\xef\x95\xb4" // U+f574 +#define ICON_FA_FILE_AUDIO "\xef\x87\x87" // U+f1c7 +#define ICON_FA_FILE_CIRCLE_CHECK "\xee\x96\xa0" // U+e5a0 +#define ICON_FA_FILE_CIRCLE_EXCLAMATION "\xee\x93\xab" // U+e4eb +#define ICON_FA_FILE_CIRCLE_MINUS "\xee\x93\xad" // U+e4ed +#define ICON_FA_FILE_CIRCLE_PLUS "\xee\x92\x94" // U+e494 +#define ICON_FA_FILE_CIRCLE_QUESTION "\xee\x93\xaf" // U+e4ef +#define ICON_FA_FILE_CIRCLE_XMARK "\xee\x96\xa1" // U+e5a1 +#define ICON_FA_FILE_CODE "\xef\x87\x89" // U+f1c9 +#define ICON_FA_FILE_CONTRACT "\xef\x95\xac" // U+f56c +#define ICON_FA_FILE_CSV "\xef\x9b\x9d" // U+f6dd +#define ICON_FA_FILE_EXCEL "\xef\x87\x83" // U+f1c3 +#define ICON_FA_FILE_EXPORT "\xef\x95\xae" // U+f56e +#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+f1c5 +#define ICON_FA_FILE_IMPORT "\xef\x95\xaf" // U+f56f +#define ICON_FA_FILE_INVOICE "\xef\x95\xb0" // U+f570 +#define ICON_FA_FILE_INVOICE_DOLLAR "\xef\x95\xb1" // U+f571 +#define ICON_FA_FILE_LINES "\xef\x85\x9c" // U+f15c +#define ICON_FA_FILE_MEDICAL "\xef\x91\xb7" // U+f477 +#define ICON_FA_FILE_PDF "\xef\x87\x81" // U+f1c1 +#define ICON_FA_FILE_PEN "\xef\x8c\x9c" // U+f31c +#define ICON_FA_FILE_POWERPOINT "\xef\x87\x84" // U+f1c4 +#define ICON_FA_FILE_PRESCRIPTION "\xef\x95\xb2" // U+f572 +#define ICON_FA_FILE_SHIELD "\xee\x93\xb0" // U+e4f0 +#define ICON_FA_FILE_SIGNATURE "\xef\x95\xb3" // U+f573 +#define ICON_FA_FILE_VIDEO "\xef\x87\x88" // U+f1c8 +#define ICON_FA_FILE_WAVEFORM "\xef\x91\xb8" // U+f478 +#define ICON_FA_FILE_WORD "\xef\x87\x82" // U+f1c2 +#define ICON_FA_FILE_ZIPPER "\xef\x87\x86" // U+f1c6 +#define ICON_FA_FILL "\xef\x95\xb5" // U+f575 +#define ICON_FA_FILL_DRIP "\xef\x95\xb6" // U+f576 +#define ICON_FA_FILM "\xef\x80\x88" // U+f008 +#define ICON_FA_FILTER "\xef\x82\xb0" // U+f0b0 +#define ICON_FA_FILTER_CIRCLE_DOLLAR "\xef\x99\xa2" // U+f662 +#define ICON_FA_FILTER_CIRCLE_XMARK "\xee\x85\xbb" // U+e17b +#define ICON_FA_FINGERPRINT "\xef\x95\xb7" // U+f577 +#define ICON_FA_FIRE "\xef\x81\xad" // U+f06d +#define ICON_FA_FIRE_BURNER "\xee\x93\xb1" // U+e4f1 +#define ICON_FA_FIRE_EXTINGUISHER "\xef\x84\xb4" // U+f134 +#define ICON_FA_FIRE_FLAME_CURVED "\xef\x9f\xa4" // U+f7e4 +#define ICON_FA_FIRE_FLAME_SIMPLE "\xef\x91\xaa" // U+f46a +#define ICON_FA_FISH "\xef\x95\xb8" // U+f578 +#define ICON_FA_FISH_FINS "\xee\x93\xb2" // U+e4f2 +#define ICON_FA_FLAG "\xef\x80\xa4" // U+f024 +#define ICON_FA_FLAG_CHECKERED "\xef\x84\x9e" // U+f11e +#define ICON_FA_FLAG_USA "\xef\x9d\x8d" // U+f74d +#define ICON_FA_FLASK "\xef\x83\x83" // U+f0c3 +#define ICON_FA_FLASK_VIAL "\xee\x93\xb3" // U+e4f3 +#define ICON_FA_FLOPPY_DISK "\xef\x83\x87" // U+f0c7 +#define ICON_FA_FLORIN_SIGN "\xee\x86\x84" // U+e184 +#define ICON_FA_FOLDER "\xef\x81\xbb" // U+f07b +#define ICON_FA_FOLDER_CLOSED "\xee\x86\x85" // U+e185 +#define ICON_FA_FOLDER_MINUS "\xef\x99\x9d" // U+f65d +#define ICON_FA_FOLDER_OPEN "\xef\x81\xbc" // U+f07c +#define ICON_FA_FOLDER_PLUS "\xef\x99\x9e" // U+f65e +#define ICON_FA_FOLDER_TREE "\xef\xa0\x82" // U+f802 +#define ICON_FA_FONT "\xef\x80\xb1" // U+f031 +#define ICON_FA_FONT_AWESOME "\xef\x8a\xb4" // U+f2b4 +#define ICON_FA_FOOTBALL "\xef\x91\x8e" // U+f44e +#define ICON_FA_FORWARD "\xef\x81\x8e" // U+f04e +#define ICON_FA_FORWARD_FAST "\xef\x81\x90" // U+f050 +#define ICON_FA_FORWARD_STEP "\xef\x81\x91" // U+f051 +#define ICON_FA_FRANC_SIGN "\xee\x86\x8f" // U+e18f +#define ICON_FA_FROG "\xef\x94\xae" // U+f52e +#define ICON_FA_FUTBOL "\xef\x87\xa3" // U+f1e3 +#define ICON_FA_G "G" // U+0047 +#define ICON_FA_GAMEPAD "\xef\x84\x9b" // U+f11b +#define ICON_FA_GAS_PUMP "\xef\x94\xaf" // U+f52f +#define ICON_FA_GAUGE "\xef\x98\xa4" // U+f624 +#define ICON_FA_GAUGE_HIGH "\xef\x98\xa5" // U+f625 +#define ICON_FA_GAUGE_SIMPLE "\xef\x98\xa9" // U+f629 +#define ICON_FA_GAUGE_SIMPLE_HIGH "\xef\x98\xaa" // U+f62a +#define ICON_FA_GAVEL "\xef\x83\xa3" // U+f0e3 +#define ICON_FA_GEAR "\xef\x80\x93" // U+f013 +#define ICON_FA_GEARS "\xef\x82\x85" // U+f085 +#define ICON_FA_GEM "\xef\x8e\xa5" // U+f3a5 +#define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d +#define ICON_FA_GHOST "\xef\x9b\xa2" // U+f6e2 +#define ICON_FA_GIFT "\xef\x81\xab" // U+f06b +#define ICON_FA_GIFTS "\xef\x9e\x9c" // U+f79c +#define ICON_FA_GLASS_WATER "\xee\x93\xb4" // U+e4f4 +#define ICON_FA_GLASS_WATER_DROPLET "\xee\x93\xb5" // U+e4f5 +#define ICON_FA_GLASSES "\xef\x94\xb0" // U+f530 +#define ICON_FA_GLOBE "\xef\x82\xac" // U+f0ac +#define ICON_FA_GOLF_BALL_TEE "\xef\x91\x90" // U+f450 +#define ICON_FA_GOPURAM "\xef\x99\xa4" // U+f664 +#define ICON_FA_GRADUATION_CAP "\xef\x86\x9d" // U+f19d +#define ICON_FA_GREATER_THAN ">" // U+003e +#define ICON_FA_GREATER_THAN_EQUAL "\xef\x94\xb2" // U+f532 +#define ICON_FA_GRIP "\xef\x96\x8d" // U+f58d +#define ICON_FA_GRIP_LINES "\xef\x9e\xa4" // U+f7a4 +#define ICON_FA_GRIP_LINES_VERTICAL "\xef\x9e\xa5" // U+f7a5 +#define ICON_FA_GRIP_VERTICAL "\xef\x96\x8e" // U+f58e +#define ICON_FA_GROUP_ARROWS_ROTATE "\xee\x93\xb6" // U+e4f6 +#define ICON_FA_GUARANI_SIGN "\xee\x86\x9a" // U+e19a +#define ICON_FA_GUITAR "\xef\x9e\xa6" // U+f7a6 +#define ICON_FA_GUN "\xee\x86\x9b" // U+e19b +#define ICON_FA_H "H" // U+0048 +#define ICON_FA_HAMMER "\xef\x9b\xa3" // U+f6e3 +#define ICON_FA_HAMSA "\xef\x99\xa5" // U+f665 +#define ICON_FA_HAND "\xef\x89\x96" // U+f256 +#define ICON_FA_HAND_BACK_FIST "\xef\x89\x95" // U+f255 +#define ICON_FA_HAND_DOTS "\xef\x91\xa1" // U+f461 +#define ICON_FA_HAND_FIST "\xef\x9b\x9e" // U+f6de +#define ICON_FA_HAND_HOLDING "\xef\x92\xbd" // U+f4bd +#define ICON_FA_HAND_HOLDING_DOLLAR "\xef\x93\x80" // U+f4c0 +#define ICON_FA_HAND_HOLDING_DROPLET "\xef\x93\x81" // U+f4c1 +#define ICON_FA_HAND_HOLDING_HAND "\xee\x93\xb7" // U+e4f7 +#define ICON_FA_HAND_HOLDING_HEART "\xef\x92\xbe" // U+f4be +#define ICON_FA_HAND_HOLDING_MEDICAL "\xee\x81\x9c" // U+e05c +#define ICON_FA_HAND_LIZARD "\xef\x89\x98" // U+f258 +#define ICON_FA_HAND_MIDDLE_FINGER "\xef\xa0\x86" // U+f806 +#define ICON_FA_HAND_PEACE "\xef\x89\x9b" // U+f25b +#define ICON_FA_HAND_POINT_DOWN "\xef\x82\xa7" // U+f0a7 +#define ICON_FA_HAND_POINT_LEFT "\xef\x82\xa5" // U+f0a5 +#define ICON_FA_HAND_POINT_RIGHT "\xef\x82\xa4" // U+f0a4 +#define ICON_FA_HAND_POINT_UP "\xef\x82\xa6" // U+f0a6 +#define ICON_FA_HAND_POINTER "\xef\x89\x9a" // U+f25a +#define ICON_FA_HAND_SCISSORS "\xef\x89\x97" // U+f257 +#define ICON_FA_HAND_SPARKLES "\xee\x81\x9d" // U+e05d +#define ICON_FA_HAND_SPOCK "\xef\x89\x99" // U+f259 +#define ICON_FA_HANDCUFFS "\xee\x93\xb8" // U+e4f8 +#define ICON_FA_HANDS "\xef\x8a\xa7" // U+f2a7 +#define ICON_FA_HANDS_ASL_INTERPRETING "\xef\x8a\xa3" // U+f2a3 +#define ICON_FA_HANDS_BOUND "\xee\x93\xb9" // U+e4f9 +#define ICON_FA_HANDS_BUBBLES "\xee\x81\x9e" // U+e05e +#define ICON_FA_HANDS_CLAPPING "\xee\x86\xa8" // U+e1a8 +#define ICON_FA_HANDS_HOLDING "\xef\x93\x82" // U+f4c2 +#define ICON_FA_HANDS_HOLDING_CHILD "\xee\x93\xba" // U+e4fa +#define ICON_FA_HANDS_HOLDING_CIRCLE "\xee\x93\xbb" // U+e4fb +#define ICON_FA_HANDS_PRAYING "\xef\x9a\x84" // U+f684 +#define ICON_FA_HANDSHAKE "\xef\x8a\xb5" // U+f2b5 +#define ICON_FA_HANDSHAKE_ANGLE "\xef\x93\x84" // U+f4c4 +#define ICON_FA_HANDSHAKE_SIMPLE "\xef\x93\x86" // U+f4c6 +#define ICON_FA_HANDSHAKE_SIMPLE_SLASH "\xee\x81\x9f" // U+e05f +#define ICON_FA_HANDSHAKE_SLASH "\xee\x81\xa0" // U+e060 +#define ICON_FA_HANUKIAH "\xef\x9b\xa6" // U+f6e6 +#define ICON_FA_HARD_DRIVE "\xef\x82\xa0" // U+f0a0 +#define ICON_FA_HASHTAG "#" // U+0023 +#define ICON_FA_HAT_COWBOY "\xef\xa3\x80" // U+f8c0 +#define ICON_FA_HAT_COWBOY_SIDE "\xef\xa3\x81" // U+f8c1 +#define ICON_FA_HAT_WIZARD "\xef\x9b\xa8" // U+f6e8 +#define ICON_FA_HEAD_SIDE_COUGH "\xee\x81\xa1" // U+e061 +#define ICON_FA_HEAD_SIDE_COUGH_SLASH "\xee\x81\xa2" // U+e062 +#define ICON_FA_HEAD_SIDE_MASK "\xee\x81\xa3" // U+e063 +#define ICON_FA_HEAD_SIDE_VIRUS "\xee\x81\xa4" // U+e064 +#define ICON_FA_HEADING "\xef\x87\x9c" // U+f1dc +#define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025 +#define ICON_FA_HEADPHONES_SIMPLE "\xef\x96\x8f" // U+f58f +#define ICON_FA_HEADSET "\xef\x96\x90" // U+f590 +#define ICON_FA_HEART "\xef\x80\x84" // U+f004 +#define ICON_FA_HEART_CIRCLE_BOLT "\xee\x93\xbc" // U+e4fc +#define ICON_FA_HEART_CIRCLE_CHECK "\xee\x93\xbd" // U+e4fd +#define ICON_FA_HEART_CIRCLE_EXCLAMATION "\xee\x93\xbe" // U+e4fe +#define ICON_FA_HEART_CIRCLE_MINUS "\xee\x93\xbf" // U+e4ff +#define ICON_FA_HEART_CIRCLE_PLUS "\xee\x94\x80" // U+e500 +#define ICON_FA_HEART_CIRCLE_XMARK "\xee\x94\x81" // U+e501 +#define ICON_FA_HEART_CRACK "\xef\x9e\xa9" // U+f7a9 +#define ICON_FA_HEART_PULSE "\xef\x88\x9e" // U+f21e +#define ICON_FA_HELICOPTER "\xef\x94\xb3" // U+f533 +#define ICON_FA_HELICOPTER_SYMBOL "\xee\x94\x82" // U+e502 +#define ICON_FA_HELMET_SAFETY "\xef\xa0\x87" // U+f807 +#define ICON_FA_HELMET_UN "\xee\x94\x83" // U+e503 +#define ICON_FA_HIGHLIGHTER "\xef\x96\x91" // U+f591 +#define ICON_FA_HILL_AVALANCHE "\xee\x94\x87" // U+e507 +#define ICON_FA_HILL_ROCKSLIDE "\xee\x94\x88" // U+e508 +#define ICON_FA_HIPPO "\xef\x9b\xad" // U+f6ed +#define ICON_FA_HOCKEY_PUCK "\xef\x91\x93" // U+f453 +#define ICON_FA_HOLLY_BERRY "\xef\x9e\xaa" // U+f7aa +#define ICON_FA_HORSE "\xef\x9b\xb0" // U+f6f0 +#define ICON_FA_HORSE_HEAD "\xef\x9e\xab" // U+f7ab +#define ICON_FA_HOSPITAL "\xef\x83\xb8" // U+f0f8 +#define ICON_FA_HOSPITAL_USER "\xef\xa0\x8d" // U+f80d +#define ICON_FA_HOT_TUB_PERSON "\xef\x96\x93" // U+f593 +#define ICON_FA_HOTDOG "\xef\xa0\x8f" // U+f80f +#define ICON_FA_HOTEL "\xef\x96\x94" // U+f594 +#define ICON_FA_HOURGLASS "\xef\x89\x94" // U+f254 +#define ICON_FA_HOURGLASS_END "\xef\x89\x93" // U+f253 +#define ICON_FA_HOURGLASS_HALF "\xef\x89\x92" // U+f252 +#define ICON_FA_HOURGLASS_START "\xef\x89\x91" // U+f251 +#define ICON_FA_HOUSE "\xef\x80\x95" // U+f015 +#define ICON_FA_HOUSE_CHIMNEY "\xee\x8e\xaf" // U+e3af +#define ICON_FA_HOUSE_CHIMNEY_CRACK "\xef\x9b\xb1" // U+f6f1 +#define ICON_FA_HOUSE_CHIMNEY_MEDICAL "\xef\x9f\xb2" // U+f7f2 +#define ICON_FA_HOUSE_CHIMNEY_USER "\xee\x81\xa5" // U+e065 +#define ICON_FA_HOUSE_CHIMNEY_WINDOW "\xee\x80\x8d" // U+e00d +#define ICON_FA_HOUSE_CIRCLE_CHECK "\xee\x94\x89" // U+e509 +#define ICON_FA_HOUSE_CIRCLE_EXCLAMATION "\xee\x94\x8a" // U+e50a +#define ICON_FA_HOUSE_CIRCLE_XMARK "\xee\x94\x8b" // U+e50b +#define ICON_FA_HOUSE_CRACK "\xee\x8e\xb1" // U+e3b1 +#define ICON_FA_HOUSE_FIRE "\xee\x94\x8c" // U+e50c +#define ICON_FA_HOUSE_FLAG "\xee\x94\x8d" // U+e50d +#define ICON_FA_HOUSE_FLOOD_WATER "\xee\x94\x8e" // U+e50e +#define ICON_FA_HOUSE_FLOOD_WATER_CIRCLE_ARROW_RIGHT "\xee\x94\x8f" // U+e50f +#define ICON_FA_HOUSE_LAPTOP "\xee\x81\xa6" // U+e066 +#define ICON_FA_HOUSE_LOCK "\xee\x94\x90" // U+e510 +#define ICON_FA_HOUSE_MEDICAL "\xee\x8e\xb2" // U+e3b2 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_CHECK "\xee\x94\x91" // U+e511 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_EXCLAMATION "\xee\x94\x92" // U+e512 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_XMARK "\xee\x94\x93" // U+e513 +#define ICON_FA_HOUSE_MEDICAL_FLAG "\xee\x94\x94" // U+e514 +#define ICON_FA_HOUSE_SIGNAL "\xee\x80\x92" // U+e012 +#define ICON_FA_HOUSE_TSUNAMI "\xee\x94\x95" // U+e515 +#define ICON_FA_HOUSE_USER "\xee\x86\xb0" // U+e1b0 +#define ICON_FA_HRYVNIA_SIGN "\xef\x9b\xb2" // U+f6f2 +#define ICON_FA_HURRICANE "\xef\x9d\x91" // U+f751 +#define ICON_FA_I "I" // U+0049 +#define ICON_FA_I_CURSOR "\xef\x89\x86" // U+f246 +#define ICON_FA_ICE_CREAM "\xef\xa0\x90" // U+f810 +#define ICON_FA_ICICLES "\xef\x9e\xad" // U+f7ad +#define ICON_FA_ICONS "\xef\xa1\xad" // U+f86d +#define ICON_FA_ID_BADGE "\xef\x8b\x81" // U+f2c1 +#define ICON_FA_ID_CARD "\xef\x8b\x82" // U+f2c2 +#define ICON_FA_ID_CARD_CLIP "\xef\x91\xbf" // U+f47f +#define ICON_FA_IGLOO "\xef\x9e\xae" // U+f7ae +#define ICON_FA_IMAGE "\xef\x80\xbe" // U+f03e +#define ICON_FA_IMAGE_PORTRAIT "\xef\x8f\xa0" // U+f3e0 +#define ICON_FA_IMAGES "\xef\x8c\x82" // U+f302 +#define ICON_FA_INBOX "\xef\x80\x9c" // U+f01c +#define ICON_FA_INDENT "\xef\x80\xbc" // U+f03c +#define ICON_FA_INDIAN_RUPEE_SIGN "\xee\x86\xbc" // U+e1bc +#define ICON_FA_INDUSTRY "\xef\x89\xb5" // U+f275 +#define ICON_FA_INFINITY "\xef\x94\xb4" // U+f534 +#define ICON_FA_INFO "\xef\x84\xa9" // U+f129 +#define ICON_FA_ITALIC "\xef\x80\xb3" // U+f033 +#define ICON_FA_J "J" // U+004a +#define ICON_FA_JAR "\xee\x94\x96" // U+e516 +#define ICON_FA_JAR_WHEAT "\xee\x94\x97" // U+e517 +#define ICON_FA_JEDI "\xef\x99\xa9" // U+f669 +#define ICON_FA_JET_FIGHTER "\xef\x83\xbb" // U+f0fb +#define ICON_FA_JET_FIGHTER_UP "\xee\x94\x98" // U+e518 +#define ICON_FA_JOINT "\xef\x96\x95" // U+f595 +#define ICON_FA_JUG_DETERGENT "\xee\x94\x99" // U+e519 +#define ICON_FA_K "K" // U+004b +#define ICON_FA_KAABA "\xef\x99\xab" // U+f66b +#define ICON_FA_KEY "\xef\x82\x84" // U+f084 +#define ICON_FA_KEYBOARD "\xef\x84\x9c" // U+f11c +#define ICON_FA_KHANDA "\xef\x99\xad" // U+f66d +#define ICON_FA_KIP_SIGN "\xee\x87\x84" // U+e1c4 +#define ICON_FA_KIT_MEDICAL "\xef\x91\xb9" // U+f479 +#define ICON_FA_KITCHEN_SET "\xee\x94\x9a" // U+e51a +#define ICON_FA_KIWI_BIRD "\xef\x94\xb5" // U+f535 +#define ICON_FA_L "L" // U+004c +#define ICON_FA_LAND_MINE_ON "\xee\x94\x9b" // U+e51b +#define ICON_FA_LANDMARK "\xef\x99\xaf" // U+f66f +#define ICON_FA_LANDMARK_DOME "\xef\x9d\x92" // U+f752 +#define ICON_FA_LANDMARK_FLAG "\xee\x94\x9c" // U+e51c +#define ICON_FA_LANGUAGE "\xef\x86\xab" // U+f1ab +#define ICON_FA_LAPTOP "\xef\x84\x89" // U+f109 +#define ICON_FA_LAPTOP_CODE "\xef\x97\xbc" // U+f5fc +#define ICON_FA_LAPTOP_FILE "\xee\x94\x9d" // U+e51d +#define ICON_FA_LAPTOP_MEDICAL "\xef\xa0\x92" // U+f812 +#define ICON_FA_LARI_SIGN "\xee\x87\x88" // U+e1c8 +#define ICON_FA_LAYER_GROUP "\xef\x97\xbd" // U+f5fd +#define ICON_FA_LEAF "\xef\x81\xac" // U+f06c +#define ICON_FA_LEFT_LONG "\xef\x8c\x8a" // U+f30a +#define ICON_FA_LEFT_RIGHT "\xef\x8c\xb7" // U+f337 +#define ICON_FA_LEMON "\xef\x82\x94" // U+f094 +#define ICON_FA_LESS_THAN "<" // U+003c +#define ICON_FA_LESS_THAN_EQUAL "\xef\x94\xb7" // U+f537 +#define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd +#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+f0eb +#define ICON_FA_LINES_LEANING "\xee\x94\x9e" // U+e51e +#define ICON_FA_LINK "\xef\x83\x81" // U+f0c1 +#define ICON_FA_LINK_SLASH "\xef\x84\xa7" // U+f127 +#define ICON_FA_LIRA_SIGN "\xef\x86\x95" // U+f195 +#define ICON_FA_LIST "\xef\x80\xba" // U+f03a +#define ICON_FA_LIST_CHECK "\xef\x82\xae" // U+f0ae +#define ICON_FA_LIST_OL "\xef\x83\x8b" // U+f0cb +#define ICON_FA_LIST_UL "\xef\x83\x8a" // U+f0ca +#define ICON_FA_LITECOIN_SIGN "\xee\x87\x93" // U+e1d3 +#define ICON_FA_LOCATION_ARROW "\xef\x84\xa4" // U+f124 +#define ICON_FA_LOCATION_CROSSHAIRS "\xef\x98\x81" // U+f601 +#define ICON_FA_LOCATION_DOT "\xef\x8f\x85" // U+f3c5 +#define ICON_FA_LOCATION_PIN "\xef\x81\x81" // U+f041 +#define ICON_FA_LOCATION_PIN_LOCK "\xee\x94\x9f" // U+e51f +#define ICON_FA_LOCK "\xef\x80\xa3" // U+f023 +#define ICON_FA_LOCK_OPEN "\xef\x8f\x81" // U+f3c1 +#define ICON_FA_LOCUST "\xee\x94\xa0" // U+e520 +#define ICON_FA_LUNGS "\xef\x98\x84" // U+f604 +#define ICON_FA_LUNGS_VIRUS "\xee\x81\xa7" // U+e067 +#define ICON_FA_M "M" // U+004d +#define ICON_FA_MAGNET "\xef\x81\xb6" // U+f076 +#define ICON_FA_MAGNIFYING_GLASS "\xef\x80\x82" // U+f002 +#define ICON_FA_MAGNIFYING_GLASS_ARROW_RIGHT "\xee\x94\xa1" // U+e521 +#define ICON_FA_MAGNIFYING_GLASS_CHART "\xee\x94\xa2" // U+e522 +#define ICON_FA_MAGNIFYING_GLASS_DOLLAR "\xef\x9a\x88" // U+f688 +#define ICON_FA_MAGNIFYING_GLASS_LOCATION "\xef\x9a\x89" // U+f689 +#define ICON_FA_MAGNIFYING_GLASS_MINUS "\xef\x80\x90" // U+f010 +#define ICON_FA_MAGNIFYING_GLASS_PLUS "\xef\x80\x8e" // U+f00e +#define ICON_FA_MANAT_SIGN "\xee\x87\x95" // U+e1d5 +#define ICON_FA_MAP "\xef\x89\xb9" // U+f279 +#define ICON_FA_MAP_LOCATION "\xef\x96\x9f" // U+f59f +#define ICON_FA_MAP_LOCATION_DOT "\xef\x96\xa0" // U+f5a0 +#define ICON_FA_MAP_PIN "\xef\x89\xb6" // U+f276 +#define ICON_FA_MARKER "\xef\x96\xa1" // U+f5a1 +#define ICON_FA_MARS "\xef\x88\xa2" // U+f222 +#define ICON_FA_MARS_AND_VENUS "\xef\x88\xa4" // U+f224 +#define ICON_FA_MARS_AND_VENUS_BURST "\xee\x94\xa3" // U+e523 +#define ICON_FA_MARS_DOUBLE "\xef\x88\xa7" // U+f227 +#define ICON_FA_MARS_STROKE "\xef\x88\xa9" // U+f229 +#define ICON_FA_MARS_STROKE_RIGHT "\xef\x88\xab" // U+f22b +#define ICON_FA_MARS_STROKE_UP "\xef\x88\xaa" // U+f22a +#define ICON_FA_MARTINI_GLASS "\xef\x95\xbb" // U+f57b +#define ICON_FA_MARTINI_GLASS_CITRUS "\xef\x95\xa1" // U+f561 +#define ICON_FA_MARTINI_GLASS_EMPTY "\xef\x80\x80" // U+f000 +#define ICON_FA_MASK "\xef\x9b\xba" // U+f6fa +#define ICON_FA_MASK_FACE "\xee\x87\x97" // U+e1d7 +#define ICON_FA_MASK_VENTILATOR "\xee\x94\xa4" // U+e524 +#define ICON_FA_MASKS_THEATER "\xef\x98\xb0" // U+f630 +#define ICON_FA_MATTRESS_PILLOW "\xee\x94\xa5" // U+e525 +#define ICON_FA_MAXIMIZE "\xef\x8c\x9e" // U+f31e +#define ICON_FA_MEDAL "\xef\x96\xa2" // U+f5a2 +#define ICON_FA_MEMORY "\xef\x94\xb8" // U+f538 +#define ICON_FA_MENORAH "\xef\x99\xb6" // U+f676 +#define ICON_FA_MERCURY "\xef\x88\xa3" // U+f223 +#define ICON_FA_MESSAGE "\xef\x89\xba" // U+f27a +#define ICON_FA_METEOR "\xef\x9d\x93" // U+f753 +#define ICON_FA_MICROCHIP "\xef\x8b\x9b" // U+f2db +#define ICON_FA_MICROPHONE "\xef\x84\xb0" // U+f130 +#define ICON_FA_MICROPHONE_LINES "\xef\x8f\x89" // U+f3c9 +#define ICON_FA_MICROPHONE_LINES_SLASH "\xef\x94\xb9" // U+f539 +#define ICON_FA_MICROPHONE_SLASH "\xef\x84\xb1" // U+f131 +#define ICON_FA_MICROSCOPE "\xef\x98\x90" // U+f610 +#define ICON_FA_MILL_SIGN "\xee\x87\xad" // U+e1ed +#define ICON_FA_MINIMIZE "\xef\x9e\x8c" // U+f78c +#define ICON_FA_MINUS "\xef\x81\xa8" // U+f068 +#define ICON_FA_MITTEN "\xef\x9e\xb5" // U+f7b5 +#define ICON_FA_MOBILE "\xef\x8f\x8e" // U+f3ce +#define ICON_FA_MOBILE_BUTTON "\xef\x84\x8b" // U+f10b +#define ICON_FA_MOBILE_RETRO "\xee\x94\xa7" // U+e527 +#define ICON_FA_MOBILE_SCREEN "\xef\x8f\x8f" // U+f3cf +#define ICON_FA_MOBILE_SCREEN_BUTTON "\xef\x8f\x8d" // U+f3cd +#define ICON_FA_MONEY_BILL "\xef\x83\x96" // U+f0d6 +#define ICON_FA_MONEY_BILL_1 "\xef\x8f\x91" // U+f3d1 +#define ICON_FA_MONEY_BILL_1_WAVE "\xef\x94\xbb" // U+f53b +#define ICON_FA_MONEY_BILL_TRANSFER "\xee\x94\xa8" // U+e528 +#define ICON_FA_MONEY_BILL_TREND_UP "\xee\x94\xa9" // U+e529 +#define ICON_FA_MONEY_BILL_WAVE "\xef\x94\xba" // U+f53a +#define ICON_FA_MONEY_BILL_WHEAT "\xee\x94\xaa" // U+e52a +#define ICON_FA_MONEY_BILLS "\xee\x87\xb3" // U+e1f3 +#define ICON_FA_MONEY_CHECK "\xef\x94\xbc" // U+f53c +#define ICON_FA_MONEY_CHECK_DOLLAR "\xef\x94\xbd" // U+f53d +#define ICON_FA_MONUMENT "\xef\x96\xa6" // U+f5a6 +#define ICON_FA_MOON "\xef\x86\x86" // U+f186 +#define ICON_FA_MORTAR_PESTLE "\xef\x96\xa7" // U+f5a7 +#define ICON_FA_MOSQUE "\xef\x99\xb8" // U+f678 +#define ICON_FA_MOSQUITO "\xee\x94\xab" // U+e52b +#define ICON_FA_MOSQUITO_NET "\xee\x94\xac" // U+e52c +#define ICON_FA_MOTORCYCLE "\xef\x88\x9c" // U+f21c +#define ICON_FA_MOUND "\xee\x94\xad" // U+e52d +#define ICON_FA_MOUNTAIN "\xef\x9b\xbc" // U+f6fc +#define ICON_FA_MOUNTAIN_CITY "\xee\x94\xae" // U+e52e +#define ICON_FA_MOUNTAIN_SUN "\xee\x94\xaf" // U+e52f +#define ICON_FA_MUG_HOT "\xef\x9e\xb6" // U+f7b6 +#define ICON_FA_MUG_SAUCER "\xef\x83\xb4" // U+f0f4 +#define ICON_FA_MUSIC "\xef\x80\x81" // U+f001 +#define ICON_FA_N "N" // U+004e +#define ICON_FA_NAIRA_SIGN "\xee\x87\xb6" // U+e1f6 +#define ICON_FA_NETWORK_WIRED "\xef\x9b\xbf" // U+f6ff +#define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c +#define ICON_FA_NEWSPAPER "\xef\x87\xaa" // U+f1ea +#define ICON_FA_NOT_EQUAL "\xef\x94\xbe" // U+f53e +#define ICON_FA_NOTDEF "\xee\x87\xbe" // U+e1fe +#define ICON_FA_NOTE_STICKY "\xef\x89\x89" // U+f249 +#define ICON_FA_NOTES_MEDICAL "\xef\x92\x81" // U+f481 +#define ICON_FA_O "O" // U+004f +#define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247 +#define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248 +#define ICON_FA_OIL_CAN "\xef\x98\x93" // U+f613 +#define ICON_FA_OIL_WELL "\xee\x94\xb2" // U+e532 +#define ICON_FA_OM "\xef\x99\xb9" // U+f679 +#define ICON_FA_OTTER "\xef\x9c\x80" // U+f700 +#define ICON_FA_OUTDENT "\xef\x80\xbb" // U+f03b +#define ICON_FA_P "P" // U+0050 +#define ICON_FA_PAGER "\xef\xa0\x95" // U+f815 +#define ICON_FA_PAINT_ROLLER "\xef\x96\xaa" // U+f5aa +#define ICON_FA_PAINTBRUSH "\xef\x87\xbc" // U+f1fc +#define ICON_FA_PALETTE "\xef\x94\xbf" // U+f53f +#define ICON_FA_PALLET "\xef\x92\x82" // U+f482 +#define ICON_FA_PANORAMA "\xee\x88\x89" // U+e209 +#define ICON_FA_PAPER_PLANE "\xef\x87\x98" // U+f1d8 +#define ICON_FA_PAPERCLIP "\xef\x83\x86" // U+f0c6 +#define ICON_FA_PARACHUTE_BOX "\xef\x93\x8d" // U+f4cd +#define ICON_FA_PARAGRAPH "\xef\x87\x9d" // U+f1dd +#define ICON_FA_PASSPORT "\xef\x96\xab" // U+f5ab +#define ICON_FA_PASTE "\xef\x83\xaa" // U+f0ea +#define ICON_FA_PAUSE "\xef\x81\x8c" // U+f04c +#define ICON_FA_PAW "\xef\x86\xb0" // U+f1b0 +#define ICON_FA_PEACE "\xef\x99\xbc" // U+f67c +#define ICON_FA_PEN "\xef\x8c\x84" // U+f304 +#define ICON_FA_PEN_CLIP "\xef\x8c\x85" // U+f305 +#define ICON_FA_PEN_FANCY "\xef\x96\xac" // U+f5ac +#define ICON_FA_PEN_NIB "\xef\x96\xad" // U+f5ad +#define ICON_FA_PEN_RULER "\xef\x96\xae" // U+f5ae +#define ICON_FA_PEN_TO_SQUARE "\xef\x81\x84" // U+f044 +#define ICON_FA_PENCIL "\xef\x8c\x83" // U+f303 +#define ICON_FA_PEOPLE_ARROWS "\xee\x81\xa8" // U+e068 +#define ICON_FA_PEOPLE_CARRY_BOX "\xef\x93\x8e" // U+f4ce +#define ICON_FA_PEOPLE_GROUP "\xee\x94\xb3" // U+e533 +#define ICON_FA_PEOPLE_LINE "\xee\x94\xb4" // U+e534 +#define ICON_FA_PEOPLE_PULLING "\xee\x94\xb5" // U+e535 +#define ICON_FA_PEOPLE_ROBBERY "\xee\x94\xb6" // U+e536 +#define ICON_FA_PEOPLE_ROOF "\xee\x94\xb7" // U+e537 +#define ICON_FA_PEPPER_HOT "\xef\xa0\x96" // U+f816 +#define ICON_FA_PERCENT "%" // U+0025 +#define ICON_FA_PERSON "\xef\x86\x83" // U+f183 +#define ICON_FA_PERSON_ARROW_DOWN_TO_LINE "\xee\x94\xb8" // U+e538 +#define ICON_FA_PERSON_ARROW_UP_FROM_LINE "\xee\x94\xb9" // U+e539 +#define ICON_FA_PERSON_BIKING "\xef\xa1\x8a" // U+f84a +#define ICON_FA_PERSON_BOOTH "\xef\x9d\x96" // U+f756 +#define ICON_FA_PERSON_BREASTFEEDING "\xee\x94\xba" // U+e53a +#define ICON_FA_PERSON_BURST "\xee\x94\xbb" // U+e53b +#define ICON_FA_PERSON_CANE "\xee\x94\xbc" // U+e53c +#define ICON_FA_PERSON_CHALKBOARD "\xee\x94\xbd" // U+e53d +#define ICON_FA_PERSON_CIRCLE_CHECK "\xee\x94\xbe" // U+e53e +#define ICON_FA_PERSON_CIRCLE_EXCLAMATION "\xee\x94\xbf" // U+e53f +#define ICON_FA_PERSON_CIRCLE_MINUS "\xee\x95\x80" // U+e540 +#define ICON_FA_PERSON_CIRCLE_PLUS "\xee\x95\x81" // U+e541 +#define ICON_FA_PERSON_CIRCLE_QUESTION "\xee\x95\x82" // U+e542 +#define ICON_FA_PERSON_CIRCLE_XMARK "\xee\x95\x83" // U+e543 +#define ICON_FA_PERSON_DIGGING "\xef\xa1\x9e" // U+f85e +#define ICON_FA_PERSON_DOTS_FROM_LINE "\xef\x91\xb0" // U+f470 +#define ICON_FA_PERSON_DRESS "\xef\x86\x82" // U+f182 +#define ICON_FA_PERSON_DRESS_BURST "\xee\x95\x84" // U+e544 +#define ICON_FA_PERSON_DROWNING "\xee\x95\x85" // U+e545 +#define ICON_FA_PERSON_FALLING "\xee\x95\x86" // U+e546 +#define ICON_FA_PERSON_FALLING_BURST "\xee\x95\x87" // U+e547 +#define ICON_FA_PERSON_HALF_DRESS "\xee\x95\x88" // U+e548 +#define ICON_FA_PERSON_HARASSING "\xee\x95\x89" // U+e549 +#define ICON_FA_PERSON_HIKING "\xef\x9b\xac" // U+f6ec +#define ICON_FA_PERSON_MILITARY_POINTING "\xee\x95\x8a" // U+e54a +#define ICON_FA_PERSON_MILITARY_RIFLE "\xee\x95\x8b" // U+e54b +#define ICON_FA_PERSON_MILITARY_TO_PERSON "\xee\x95\x8c" // U+e54c +#define ICON_FA_PERSON_PRAYING "\xef\x9a\x83" // U+f683 +#define ICON_FA_PERSON_PREGNANT "\xee\x8c\x9e" // U+e31e +#define ICON_FA_PERSON_RAYS "\xee\x95\x8d" // U+e54d +#define ICON_FA_PERSON_RIFLE "\xee\x95\x8e" // U+e54e +#define ICON_FA_PERSON_RUNNING "\xef\x9c\x8c" // U+f70c +#define ICON_FA_PERSON_SHELTER "\xee\x95\x8f" // U+e54f +#define ICON_FA_PERSON_SKATING "\xef\x9f\x85" // U+f7c5 +#define ICON_FA_PERSON_SKIING "\xef\x9f\x89" // U+f7c9 +#define ICON_FA_PERSON_SKIING_NORDIC "\xef\x9f\x8a" // U+f7ca +#define ICON_FA_PERSON_SNOWBOARDING "\xef\x9f\x8e" // U+f7ce +#define ICON_FA_PERSON_SWIMMING "\xef\x97\x84" // U+f5c4 +#define ICON_FA_PERSON_THROUGH_WINDOW "\xee\x96\xa9" // U+e5a9 +#define ICON_FA_PERSON_WALKING "\xef\x95\x94" // U+f554 +#define ICON_FA_PERSON_WALKING_ARROW_LOOP_LEFT "\xee\x95\x91" // U+e551 +#define ICON_FA_PERSON_WALKING_ARROW_RIGHT "\xee\x95\x92" // U+e552 +#define ICON_FA_PERSON_WALKING_DASHED_LINE_ARROW_RIGHT "\xee\x95\x93" // U+e553 +#define ICON_FA_PERSON_WALKING_LUGGAGE "\xee\x95\x94" // U+e554 +#define ICON_FA_PERSON_WALKING_WITH_CANE "\xef\x8a\x9d" // U+f29d +#define ICON_FA_PESETA_SIGN "\xee\x88\xa1" // U+e221 +#define ICON_FA_PESO_SIGN "\xee\x88\xa2" // U+e222 +#define ICON_FA_PHONE "\xef\x82\x95" // U+f095 +#define ICON_FA_PHONE_FLIP "\xef\xa1\xb9" // U+f879 +#define ICON_FA_PHONE_SLASH "\xef\x8f\x9d" // U+f3dd +#define ICON_FA_PHONE_VOLUME "\xef\x8a\xa0" // U+f2a0 +#define ICON_FA_PHOTO_FILM "\xef\xa1\xbc" // U+f87c +#define ICON_FA_PIGGY_BANK "\xef\x93\x93" // U+f4d3 +#define ICON_FA_PILLS "\xef\x92\x84" // U+f484 +#define ICON_FA_PIZZA_SLICE "\xef\xa0\x98" // U+f818 +#define ICON_FA_PLACE_OF_WORSHIP "\xef\x99\xbf" // U+f67f +#define ICON_FA_PLANE "\xef\x81\xb2" // U+f072 +#define ICON_FA_PLANE_ARRIVAL "\xef\x96\xaf" // U+f5af +#define ICON_FA_PLANE_CIRCLE_CHECK "\xee\x95\x95" // U+e555 +#define ICON_FA_PLANE_CIRCLE_EXCLAMATION "\xee\x95\x96" // U+e556 +#define ICON_FA_PLANE_CIRCLE_XMARK "\xee\x95\x97" // U+e557 +#define ICON_FA_PLANE_DEPARTURE "\xef\x96\xb0" // U+f5b0 +#define ICON_FA_PLANE_LOCK "\xee\x95\x98" // U+e558 +#define ICON_FA_PLANE_SLASH "\xee\x81\xa9" // U+e069 +#define ICON_FA_PLANE_UP "\xee\x88\xad" // U+e22d +#define ICON_FA_PLANT_WILT "\xee\x96\xaa" // U+e5aa +#define ICON_FA_PLATE_WHEAT "\xee\x95\x9a" // U+e55a +#define ICON_FA_PLAY "\xef\x81\x8b" // U+f04b +#define ICON_FA_PLUG "\xef\x87\xa6" // U+f1e6 +#define ICON_FA_PLUG_CIRCLE_BOLT "\xee\x95\x9b" // U+e55b +#define ICON_FA_PLUG_CIRCLE_CHECK "\xee\x95\x9c" // U+e55c +#define ICON_FA_PLUG_CIRCLE_EXCLAMATION "\xee\x95\x9d" // U+e55d +#define ICON_FA_PLUG_CIRCLE_MINUS "\xee\x95\x9e" // U+e55e +#define ICON_FA_PLUG_CIRCLE_PLUS "\xee\x95\x9f" // U+e55f +#define ICON_FA_PLUG_CIRCLE_XMARK "\xee\x95\xa0" // U+e560 +#define ICON_FA_PLUS "+" // U+002b +#define ICON_FA_PLUS_MINUS "\xee\x90\xbc" // U+e43c +#define ICON_FA_PODCAST "\xef\x8b\x8e" // U+f2ce +#define ICON_FA_POO "\xef\x8b\xbe" // U+f2fe +#define ICON_FA_POO_STORM "\xef\x9d\x9a" // U+f75a +#define ICON_FA_POOP "\xef\x98\x99" // U+f619 +#define ICON_FA_POWER_OFF "\xef\x80\x91" // U+f011 +#define ICON_FA_PRESCRIPTION "\xef\x96\xb1" // U+f5b1 +#define ICON_FA_PRESCRIPTION_BOTTLE "\xef\x92\x85" // U+f485 +#define ICON_FA_PRESCRIPTION_BOTTLE_MEDICAL "\xef\x92\x86" // U+f486 +#define ICON_FA_PRINT "\xef\x80\xaf" // U+f02f +#define ICON_FA_PUMP_MEDICAL "\xee\x81\xaa" // U+e06a +#define ICON_FA_PUMP_SOAP "\xee\x81\xab" // U+e06b +#define ICON_FA_PUZZLE_PIECE "\xef\x84\xae" // U+f12e +#define ICON_FA_Q "Q" // U+0051 +#define ICON_FA_QRCODE "\xef\x80\xa9" // U+f029 +#define ICON_FA_QUESTION "?" // U+003f +#define ICON_FA_QUOTE_LEFT "\xef\x84\x8d" // U+f10d +#define ICON_FA_QUOTE_RIGHT "\xef\x84\x8e" // U+f10e +#define ICON_FA_R "R" // U+0052 +#define ICON_FA_RADIATION "\xef\x9e\xb9" // U+f7b9 +#define ICON_FA_RADIO "\xef\xa3\x97" // U+f8d7 +#define ICON_FA_RAINBOW "\xef\x9d\x9b" // U+f75b +#define ICON_FA_RANKING_STAR "\xee\x95\xa1" // U+e561 +#define ICON_FA_RECEIPT "\xef\x95\x83" // U+f543 +#define ICON_FA_RECORD_VINYL "\xef\xa3\x99" // U+f8d9 +#define ICON_FA_RECTANGLE_AD "\xef\x99\x81" // U+f641 +#define ICON_FA_RECTANGLE_LIST "\xef\x80\xa2" // U+f022 +#define ICON_FA_RECTANGLE_XMARK "\xef\x90\x90" // U+f410 +#define ICON_FA_RECYCLE "\xef\x86\xb8" // U+f1b8 +#define ICON_FA_REGISTERED "\xef\x89\x9d" // U+f25d +#define ICON_FA_REPEAT "\xef\x8d\xa3" // U+f363 +#define ICON_FA_REPLY "\xef\x8f\xa5" // U+f3e5 +#define ICON_FA_REPLY_ALL "\xef\x84\xa2" // U+f122 +#define ICON_FA_REPUBLICAN "\xef\x9d\x9e" // U+f75e +#define ICON_FA_RESTROOM "\xef\x9e\xbd" // U+f7bd +#define ICON_FA_RETWEET "\xef\x81\xb9" // U+f079 +#define ICON_FA_RIBBON "\xef\x93\x96" // U+f4d6 +#define ICON_FA_RIGHT_FROM_BRACKET "\xef\x8b\xb5" // U+f2f5 +#define ICON_FA_RIGHT_LEFT "\xef\x8d\xa2" // U+f362 +#define ICON_FA_RIGHT_LONG "\xef\x8c\x8b" // U+f30b +#define ICON_FA_RIGHT_TO_BRACKET "\xef\x8b\xb6" // U+f2f6 +#define ICON_FA_RING "\xef\x9c\x8b" // U+f70b +#define ICON_FA_ROAD "\xef\x80\x98" // U+f018 +#define ICON_FA_ROAD_BARRIER "\xee\x95\xa2" // U+e562 +#define ICON_FA_ROAD_BRIDGE "\xee\x95\xa3" // U+e563 +#define ICON_FA_ROAD_CIRCLE_CHECK "\xee\x95\xa4" // U+e564 +#define ICON_FA_ROAD_CIRCLE_EXCLAMATION "\xee\x95\xa5" // U+e565 +#define ICON_FA_ROAD_CIRCLE_XMARK "\xee\x95\xa6" // U+e566 +#define ICON_FA_ROAD_LOCK "\xee\x95\xa7" // U+e567 +#define ICON_FA_ROAD_SPIKES "\xee\x95\xa8" // U+e568 +#define ICON_FA_ROBOT "\xef\x95\x84" // U+f544 +#define ICON_FA_ROCKET "\xef\x84\xb5" // U+f135 +#define ICON_FA_ROTATE "\xef\x8b\xb1" // U+f2f1 +#define ICON_FA_ROTATE_LEFT "\xef\x8b\xaa" // U+f2ea +#define ICON_FA_ROTATE_RIGHT "\xef\x8b\xb9" // U+f2f9 +#define ICON_FA_ROUTE "\xef\x93\x97" // U+f4d7 +#define ICON_FA_RSS "\xef\x82\x9e" // U+f09e +#define ICON_FA_RUBLE_SIGN "\xef\x85\x98" // U+f158 +#define ICON_FA_RUG "\xee\x95\xa9" // U+e569 +#define ICON_FA_RULER "\xef\x95\x85" // U+f545 +#define ICON_FA_RULER_COMBINED "\xef\x95\x86" // U+f546 +#define ICON_FA_RULER_HORIZONTAL "\xef\x95\x87" // U+f547 +#define ICON_FA_RULER_VERTICAL "\xef\x95\x88" // U+f548 +#define ICON_FA_RUPEE_SIGN "\xef\x85\x96" // U+f156 +#define ICON_FA_RUPIAH_SIGN "\xee\x88\xbd" // U+e23d +#define ICON_FA_S "S" // U+0053 +#define ICON_FA_SACK_DOLLAR "\xef\xa0\x9d" // U+f81d +#define ICON_FA_SACK_XMARK "\xee\x95\xaa" // U+e56a +#define ICON_FA_SAILBOAT "\xee\x91\x85" // U+e445 +#define ICON_FA_SATELLITE "\xef\x9e\xbf" // U+f7bf +#define ICON_FA_SATELLITE_DISH "\xef\x9f\x80" // U+f7c0 +#define ICON_FA_SCALE_BALANCED "\xef\x89\x8e" // U+f24e +#define ICON_FA_SCALE_UNBALANCED "\xef\x94\x95" // U+f515 +#define ICON_FA_SCALE_UNBALANCED_FLIP "\xef\x94\x96" // U+f516 +#define ICON_FA_SCHOOL "\xef\x95\x89" // U+f549 +#define ICON_FA_SCHOOL_CIRCLE_CHECK "\xee\x95\xab" // U+e56b +#define ICON_FA_SCHOOL_CIRCLE_EXCLAMATION "\xee\x95\xac" // U+e56c +#define ICON_FA_SCHOOL_CIRCLE_XMARK "\xee\x95\xad" // U+e56d +#define ICON_FA_SCHOOL_FLAG "\xee\x95\xae" // U+e56e +#define ICON_FA_SCHOOL_LOCK "\xee\x95\xaf" // U+e56f +#define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4 +#define ICON_FA_SCREWDRIVER "\xef\x95\x8a" // U+f54a +#define ICON_FA_SCREWDRIVER_WRENCH "\xef\x9f\x99" // U+f7d9 +#define ICON_FA_SCROLL "\xef\x9c\x8e" // U+f70e +#define ICON_FA_SCROLL_TORAH "\xef\x9a\xa0" // U+f6a0 +#define ICON_FA_SD_CARD "\xef\x9f\x82" // U+f7c2 +#define ICON_FA_SECTION "\xee\x91\x87" // U+e447 +#define ICON_FA_SEEDLING "\xef\x93\x98" // U+f4d8 +#define ICON_FA_SERVER "\xef\x88\xb3" // U+f233 +#define ICON_FA_SHAPES "\xef\x98\x9f" // U+f61f +#define ICON_FA_SHARE "\xef\x81\xa4" // U+f064 +#define ICON_FA_SHARE_FROM_SQUARE "\xef\x85\x8d" // U+f14d +#define ICON_FA_SHARE_NODES "\xef\x87\xa0" // U+f1e0 +#define ICON_FA_SHEET_PLASTIC "\xee\x95\xb1" // U+e571 +#define ICON_FA_SHEKEL_SIGN "\xef\x88\x8b" // U+f20b +#define ICON_FA_SHIELD "\xef\x84\xb2" // U+f132 +#define ICON_FA_SHIELD_CAT "\xee\x95\xb2" // U+e572 +#define ICON_FA_SHIELD_DOG "\xee\x95\xb3" // U+e573 +#define ICON_FA_SHIELD_HALVED "\xef\x8f\xad" // U+f3ed +#define ICON_FA_SHIELD_HEART "\xee\x95\xb4" // U+e574 +#define ICON_FA_SHIELD_VIRUS "\xee\x81\xac" // U+e06c +#define ICON_FA_SHIP "\xef\x88\x9a" // U+f21a +#define ICON_FA_SHIRT "\xef\x95\x93" // U+f553 +#define ICON_FA_SHOE_PRINTS "\xef\x95\x8b" // U+f54b +#define ICON_FA_SHOP "\xef\x95\x8f" // U+f54f +#define ICON_FA_SHOP_LOCK "\xee\x92\xa5" // U+e4a5 +#define ICON_FA_SHOP_SLASH "\xee\x81\xb0" // U+e070 +#define ICON_FA_SHOWER "\xef\x8b\x8c" // U+f2cc +#define ICON_FA_SHRIMP "\xee\x91\x88" // U+e448 +#define ICON_FA_SHUFFLE "\xef\x81\xb4" // U+f074 +#define ICON_FA_SHUTTLE_SPACE "\xef\x86\x97" // U+f197 +#define ICON_FA_SIGN_HANGING "\xef\x93\x99" // U+f4d9 +#define ICON_FA_SIGNAL "\xef\x80\x92" // U+f012 +#define ICON_FA_SIGNATURE "\xef\x96\xb7" // U+f5b7 +#define ICON_FA_SIGNS_POST "\xef\x89\xb7" // U+f277 +#define ICON_FA_SIM_CARD "\xef\x9f\x84" // U+f7c4 +#define ICON_FA_SINK "\xee\x81\xad" // U+e06d +#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8 +#define ICON_FA_SKULL "\xef\x95\x8c" // U+f54c +#define ICON_FA_SKULL_CROSSBONES "\xef\x9c\x94" // U+f714 +#define ICON_FA_SLASH "\xef\x9c\x95" // U+f715 +#define ICON_FA_SLEIGH "\xef\x9f\x8c" // U+f7cc +#define ICON_FA_SLIDERS "\xef\x87\x9e" // U+f1de +#define ICON_FA_SMOG "\xef\x9d\x9f" // U+f75f +#define ICON_FA_SMOKING "\xef\x92\x8d" // U+f48d +#define ICON_FA_SNOWFLAKE "\xef\x8b\x9c" // U+f2dc +#define ICON_FA_SNOWMAN "\xef\x9f\x90" // U+f7d0 +#define ICON_FA_SNOWPLOW "\xef\x9f\x92" // U+f7d2 +#define ICON_FA_SOAP "\xee\x81\xae" // U+e06e +#define ICON_FA_SOCKS "\xef\x9a\x96" // U+f696 +#define ICON_FA_SOLAR_PANEL "\xef\x96\xba" // U+f5ba +#define ICON_FA_SORT "\xef\x83\x9c" // U+f0dc +#define ICON_FA_SORT_DOWN "\xef\x83\x9d" // U+f0dd +#define ICON_FA_SORT_UP "\xef\x83\x9e" // U+f0de +#define ICON_FA_SPA "\xef\x96\xbb" // U+f5bb +#define ICON_FA_SPAGHETTI_MONSTER_FLYING "\xef\x99\xbb" // U+f67b +#define ICON_FA_SPELL_CHECK "\xef\xa2\x91" // U+f891 +#define ICON_FA_SPIDER "\xef\x9c\x97" // U+f717 +#define ICON_FA_SPINNER "\xef\x84\x90" // U+f110 +#define ICON_FA_SPLOTCH "\xef\x96\xbc" // U+f5bc +#define ICON_FA_SPOON "\xef\x8b\xa5" // U+f2e5 +#define ICON_FA_SPRAY_CAN "\xef\x96\xbd" // U+f5bd +#define ICON_FA_SPRAY_CAN_SPARKLES "\xef\x97\x90" // U+f5d0 +#define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8 +#define ICON_FA_SQUARE_ARROW_UP_RIGHT "\xef\x85\x8c" // U+f14c +#define ICON_FA_SQUARE_CARET_DOWN "\xef\x85\x90" // U+f150 +#define ICON_FA_SQUARE_CARET_LEFT "\xef\x86\x91" // U+f191 +#define ICON_FA_SQUARE_CARET_RIGHT "\xef\x85\x92" // U+f152 +#define ICON_FA_SQUARE_CARET_UP "\xef\x85\x91" // U+f151 +#define ICON_FA_SQUARE_CHECK "\xef\x85\x8a" // U+f14a +#define ICON_FA_SQUARE_ENVELOPE "\xef\x86\x99" // U+f199 +#define ICON_FA_SQUARE_FULL "\xef\x91\x9c" // U+f45c +#define ICON_FA_SQUARE_H "\xef\x83\xbd" // U+f0fd +#define ICON_FA_SQUARE_MINUS "\xef\x85\x86" // U+f146 +#define ICON_FA_SQUARE_NFI "\xee\x95\xb6" // U+e576 +#define ICON_FA_SQUARE_PARKING "\xef\x95\x80" // U+f540 +#define ICON_FA_SQUARE_PEN "\xef\x85\x8b" // U+f14b +#define ICON_FA_SQUARE_PERSON_CONFINED "\xee\x95\xb7" // U+e577 +#define ICON_FA_SQUARE_PHONE "\xef\x82\x98" // U+f098 +#define ICON_FA_SQUARE_PHONE_FLIP "\xef\xa1\xbb" // U+f87b +#define ICON_FA_SQUARE_PLUS "\xef\x83\xbe" // U+f0fe +#define ICON_FA_SQUARE_POLL_HORIZONTAL "\xef\x9a\x82" // U+f682 +#define ICON_FA_SQUARE_POLL_VERTICAL "\xef\x9a\x81" // U+f681 +#define ICON_FA_SQUARE_ROOT_VARIABLE "\xef\x9a\x98" // U+f698 +#define ICON_FA_SQUARE_RSS "\xef\x85\x83" // U+f143 +#define ICON_FA_SQUARE_SHARE_NODES "\xef\x87\xa1" // U+f1e1 +#define ICON_FA_SQUARE_UP_RIGHT "\xef\x8d\xa0" // U+f360 +#define ICON_FA_SQUARE_VIRUS "\xee\x95\xb8" // U+e578 +#define ICON_FA_SQUARE_XMARK "\xef\x8b\x93" // U+f2d3 +#define ICON_FA_STAFF_SNAKE "\xee\x95\xb9" // U+e579 +#define ICON_FA_STAIRS "\xee\x8a\x89" // U+e289 +#define ICON_FA_STAMP "\xef\x96\xbf" // U+f5bf +#define ICON_FA_STAPLER "\xee\x96\xaf" // U+e5af +#define ICON_FA_STAR "\xef\x80\x85" // U+f005 +#define ICON_FA_STAR_AND_CRESCENT "\xef\x9a\x99" // U+f699 +#define ICON_FA_STAR_HALF "\xef\x82\x89" // U+f089 +#define ICON_FA_STAR_HALF_STROKE "\xef\x97\x80" // U+f5c0 +#define ICON_FA_STAR_OF_DAVID "\xef\x9a\x9a" // U+f69a +#define ICON_FA_STAR_OF_LIFE "\xef\x98\xa1" // U+f621 +#define ICON_FA_STERLING_SIGN "\xef\x85\x94" // U+f154 +#define ICON_FA_STETHOSCOPE "\xef\x83\xb1" // U+f0f1 +#define ICON_FA_STOP "\xef\x81\x8d" // U+f04d +#define ICON_FA_STOPWATCH "\xef\x8b\xb2" // U+f2f2 +#define ICON_FA_STOPWATCH_20 "\xee\x81\xaf" // U+e06f +#define ICON_FA_STORE "\xef\x95\x8e" // U+f54e +#define ICON_FA_STORE_SLASH "\xee\x81\xb1" // U+e071 +#define ICON_FA_STREET_VIEW "\xef\x88\x9d" // U+f21d +#define ICON_FA_STRIKETHROUGH "\xef\x83\x8c" // U+f0cc +#define ICON_FA_STROOPWAFEL "\xef\x95\x91" // U+f551 +#define ICON_FA_SUBSCRIPT "\xef\x84\xac" // U+f12c +#define ICON_FA_SUITCASE "\xef\x83\xb2" // U+f0f2 +#define ICON_FA_SUITCASE_MEDICAL "\xef\x83\xba" // U+f0fa +#define ICON_FA_SUITCASE_ROLLING "\xef\x97\x81" // U+f5c1 +#define ICON_FA_SUN "\xef\x86\x85" // U+f185 +#define ICON_FA_SUN_PLANT_WILT "\xee\x95\xba" // U+e57a +#define ICON_FA_SUPERSCRIPT "\xef\x84\xab" // U+f12b +#define ICON_FA_SWATCHBOOK "\xef\x97\x83" // U+f5c3 +#define ICON_FA_SYNAGOGUE "\xef\x9a\x9b" // U+f69b +#define ICON_FA_SYRINGE "\xef\x92\x8e" // U+f48e +#define ICON_FA_T "T" // U+0054 +#define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce +#define ICON_FA_TABLE_CELLS "\xef\x80\x8a" // U+f00a +#define ICON_FA_TABLE_CELLS_COLUMN_LOCK "\xee\x99\xb8" // U+e678 +#define ICON_FA_TABLE_CELLS_LARGE "\xef\x80\x89" // U+f009 +#define ICON_FA_TABLE_CELLS_ROW_LOCK "\xee\x99\xba" // U+e67a +#define ICON_FA_TABLE_COLUMNS "\xef\x83\x9b" // U+f0db +#define ICON_FA_TABLE_LIST "\xef\x80\x8b" // U+f00b +#define ICON_FA_TABLE_TENNIS_PADDLE_BALL "\xef\x91\x9d" // U+f45d +#define ICON_FA_TABLET "\xef\x8f\xbb" // U+f3fb +#define ICON_FA_TABLET_BUTTON "\xef\x84\x8a" // U+f10a +#define ICON_FA_TABLET_SCREEN_BUTTON "\xef\x8f\xba" // U+f3fa +#define ICON_FA_TABLETS "\xef\x92\x90" // U+f490 +#define ICON_FA_TACHOGRAPH_DIGITAL "\xef\x95\xa6" // U+f566 +#define ICON_FA_TAG "\xef\x80\xab" // U+f02b +#define ICON_FA_TAGS "\xef\x80\xac" // U+f02c +#define ICON_FA_TAPE "\xef\x93\x9b" // U+f4db +#define ICON_FA_TARP "\xee\x95\xbb" // U+e57b +#define ICON_FA_TARP_DROPLET "\xee\x95\xbc" // U+e57c +#define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba +#define ICON_FA_TEETH "\xef\x98\xae" // U+f62e +#define ICON_FA_TEETH_OPEN "\xef\x98\xaf" // U+f62f +#define ICON_FA_TEMPERATURE_ARROW_DOWN "\xee\x80\xbf" // U+e03f +#define ICON_FA_TEMPERATURE_ARROW_UP "\xee\x81\x80" // U+e040 +#define ICON_FA_TEMPERATURE_EMPTY "\xef\x8b\x8b" // U+f2cb +#define ICON_FA_TEMPERATURE_FULL "\xef\x8b\x87" // U+f2c7 +#define ICON_FA_TEMPERATURE_HALF "\xef\x8b\x89" // U+f2c9 +#define ICON_FA_TEMPERATURE_HIGH "\xef\x9d\xa9" // U+f769 +#define ICON_FA_TEMPERATURE_LOW "\xef\x9d\xab" // U+f76b +#define ICON_FA_TEMPERATURE_QUARTER "\xef\x8b\x8a" // U+f2ca +#define ICON_FA_TEMPERATURE_THREE_QUARTERS "\xef\x8b\x88" // U+f2c8 +#define ICON_FA_TENGE_SIGN "\xef\x9f\x97" // U+f7d7 +#define ICON_FA_TENT "\xee\x95\xbd" // U+e57d +#define ICON_FA_TENT_ARROW_DOWN_TO_LINE "\xee\x95\xbe" // U+e57e +#define ICON_FA_TENT_ARROW_LEFT_RIGHT "\xee\x95\xbf" // U+e57f +#define ICON_FA_TENT_ARROW_TURN_LEFT "\xee\x96\x80" // U+e580 +#define ICON_FA_TENT_ARROWS_DOWN "\xee\x96\x81" // U+e581 +#define ICON_FA_TENTS "\xee\x96\x82" // U+e582 +#define ICON_FA_TERMINAL "\xef\x84\xa0" // U+f120 +#define ICON_FA_TEXT_HEIGHT "\xef\x80\xb4" // U+f034 +#define ICON_FA_TEXT_SLASH "\xef\xa1\xbd" // U+f87d +#define ICON_FA_TEXT_WIDTH "\xef\x80\xb5" // U+f035 +#define ICON_FA_THERMOMETER "\xef\x92\x91" // U+f491 +#define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165 +#define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164 +#define ICON_FA_THUMBTACK "\xef\x82\x8d" // U+f08d +#define ICON_FA_TICKET "\xef\x85\x85" // U+f145 +#define ICON_FA_TICKET_SIMPLE "\xef\x8f\xbf" // U+f3ff +#define ICON_FA_TIMELINE "\xee\x8a\x9c" // U+e29c +#define ICON_FA_TOGGLE_OFF "\xef\x88\x84" // U+f204 +#define ICON_FA_TOGGLE_ON "\xef\x88\x85" // U+f205 +#define ICON_FA_TOILET "\xef\x9f\x98" // U+f7d8 +#define ICON_FA_TOILET_PAPER "\xef\x9c\x9e" // U+f71e +#define ICON_FA_TOILET_PAPER_SLASH "\xee\x81\xb2" // U+e072 +#define ICON_FA_TOILET_PORTABLE "\xee\x96\x83" // U+e583 +#define ICON_FA_TOILETS_PORTABLE "\xee\x96\x84" // U+e584 +#define ICON_FA_TOOLBOX "\xef\x95\x92" // U+f552 +#define ICON_FA_TOOTH "\xef\x97\x89" // U+f5c9 +#define ICON_FA_TORII_GATE "\xef\x9a\xa1" // U+f6a1 +#define ICON_FA_TORNADO "\xef\x9d\xaf" // U+f76f +#define ICON_FA_TOWER_BROADCAST "\xef\x94\x99" // U+f519 +#define ICON_FA_TOWER_CELL "\xee\x96\x85" // U+e585 +#define ICON_FA_TOWER_OBSERVATION "\xee\x96\x86" // U+e586 +#define ICON_FA_TRACTOR "\xef\x9c\xa2" // U+f722 +#define ICON_FA_TRADEMARK "\xef\x89\x9c" // U+f25c +#define ICON_FA_TRAFFIC_LIGHT "\xef\x98\xb7" // U+f637 +#define ICON_FA_TRAILER "\xee\x81\x81" // U+e041 +#define ICON_FA_TRAIN "\xef\x88\xb8" // U+f238 +#define ICON_FA_TRAIN_SUBWAY "\xef\x88\xb9" // U+f239 +#define ICON_FA_TRAIN_TRAM "\xee\x96\xb4" // U+e5b4 +#define ICON_FA_TRANSGENDER "\xef\x88\xa5" // U+f225 +#define ICON_FA_TRASH "\xef\x87\xb8" // U+f1f8 +#define ICON_FA_TRASH_ARROW_UP "\xef\xa0\xa9" // U+f829 +#define ICON_FA_TRASH_CAN "\xef\x8b\xad" // U+f2ed +#define ICON_FA_TRASH_CAN_ARROW_UP "\xef\xa0\xaa" // U+f82a +#define ICON_FA_TREE "\xef\x86\xbb" // U+f1bb +#define ICON_FA_TREE_CITY "\xee\x96\x87" // U+e587 +#define ICON_FA_TRIANGLE_EXCLAMATION "\xef\x81\xb1" // U+f071 +#define ICON_FA_TROPHY "\xef\x82\x91" // U+f091 +#define ICON_FA_TROWEL "\xee\x96\x89" // U+e589 +#define ICON_FA_TROWEL_BRICKS "\xee\x96\x8a" // U+e58a +#define ICON_FA_TRUCK "\xef\x83\x91" // U+f0d1 +#define ICON_FA_TRUCK_ARROW_RIGHT "\xee\x96\x8b" // U+e58b +#define ICON_FA_TRUCK_DROPLET "\xee\x96\x8c" // U+e58c +#define ICON_FA_TRUCK_FAST "\xef\x92\x8b" // U+f48b +#define ICON_FA_TRUCK_FIELD "\xee\x96\x8d" // U+e58d +#define ICON_FA_TRUCK_FIELD_UN "\xee\x96\x8e" // U+e58e +#define ICON_FA_TRUCK_FRONT "\xee\x8a\xb7" // U+e2b7 +#define ICON_FA_TRUCK_MEDICAL "\xef\x83\xb9" // U+f0f9 +#define ICON_FA_TRUCK_MONSTER "\xef\x98\xbb" // U+f63b +#define ICON_FA_TRUCK_MOVING "\xef\x93\x9f" // U+f4df +#define ICON_FA_TRUCK_PICKUP "\xef\x98\xbc" // U+f63c +#define ICON_FA_TRUCK_PLANE "\xee\x96\x8f" // U+e58f +#define ICON_FA_TRUCK_RAMP_BOX "\xef\x93\x9e" // U+f4de +#define ICON_FA_TTY "\xef\x87\xa4" // U+f1e4 +#define ICON_FA_TURKISH_LIRA_SIGN "\xee\x8a\xbb" // U+e2bb +#define ICON_FA_TURN_DOWN "\xef\x8e\xbe" // U+f3be +#define ICON_FA_TURN_UP "\xef\x8e\xbf" // U+f3bf +#define ICON_FA_TV "\xef\x89\xac" // U+f26c +#define ICON_FA_U "U" // U+0055 +#define ICON_FA_UMBRELLA "\xef\x83\xa9" // U+f0e9 +#define ICON_FA_UMBRELLA_BEACH "\xef\x97\x8a" // U+f5ca +#define ICON_FA_UNDERLINE "\xef\x83\x8d" // U+f0cd +#define ICON_FA_UNIVERSAL_ACCESS "\xef\x8a\x9a" // U+f29a +#define ICON_FA_UNLOCK "\xef\x82\x9c" // U+f09c +#define ICON_FA_UNLOCK_KEYHOLE "\xef\x84\xbe" // U+f13e +#define ICON_FA_UP_DOWN "\xef\x8c\xb8" // U+f338 +#define ICON_FA_UP_DOWN_LEFT_RIGHT "\xef\x82\xb2" // U+f0b2 +#define ICON_FA_UP_LONG "\xef\x8c\x8c" // U+f30c +#define ICON_FA_UP_RIGHT_AND_DOWN_LEFT_FROM_CENTER "\xef\x90\xa4" // U+f424 +#define ICON_FA_UP_RIGHT_FROM_SQUARE "\xef\x8d\x9d" // U+f35d +#define ICON_FA_UPLOAD "\xef\x82\x93" // U+f093 +#define ICON_FA_USER "\xef\x80\x87" // U+f007 +#define ICON_FA_USER_ASTRONAUT "\xef\x93\xbb" // U+f4fb +#define ICON_FA_USER_CHECK "\xef\x93\xbc" // U+f4fc +#define ICON_FA_USER_CLOCK "\xef\x93\xbd" // U+f4fd +#define ICON_FA_USER_DOCTOR "\xef\x83\xb0" // U+f0f0 +#define ICON_FA_USER_GEAR "\xef\x93\xbe" // U+f4fe +#define ICON_FA_USER_GRADUATE "\xef\x94\x81" // U+f501 +#define ICON_FA_USER_GROUP "\xef\x94\x80" // U+f500 +#define ICON_FA_USER_INJURED "\xef\x9c\xa8" // U+f728 +#define ICON_FA_USER_LARGE "\xef\x90\x86" // U+f406 +#define ICON_FA_USER_LARGE_SLASH "\xef\x93\xba" // U+f4fa +#define ICON_FA_USER_LOCK "\xef\x94\x82" // U+f502 +#define ICON_FA_USER_MINUS "\xef\x94\x83" // U+f503 +#define ICON_FA_USER_NINJA "\xef\x94\x84" // U+f504 +#define ICON_FA_USER_NURSE "\xef\xa0\xaf" // U+f82f +#define ICON_FA_USER_PEN "\xef\x93\xbf" // U+f4ff +#define ICON_FA_USER_PLUS "\xef\x88\xb4" // U+f234 +#define ICON_FA_USER_SECRET "\xef\x88\x9b" // U+f21b +#define ICON_FA_USER_SHIELD "\xef\x94\x85" // U+f505 +#define ICON_FA_USER_SLASH "\xef\x94\x86" // U+f506 +#define ICON_FA_USER_TAG "\xef\x94\x87" // U+f507 +#define ICON_FA_USER_TIE "\xef\x94\x88" // U+f508 +#define ICON_FA_USER_XMARK "\xef\x88\xb5" // U+f235 +#define ICON_FA_USERS "\xef\x83\x80" // U+f0c0 +#define ICON_FA_USERS_BETWEEN_LINES "\xee\x96\x91" // U+e591 +#define ICON_FA_USERS_GEAR "\xef\x94\x89" // U+f509 +#define ICON_FA_USERS_LINE "\xee\x96\x92" // U+e592 +#define ICON_FA_USERS_RAYS "\xee\x96\x93" // U+e593 +#define ICON_FA_USERS_RECTANGLE "\xee\x96\x94" // U+e594 +#define ICON_FA_USERS_SLASH "\xee\x81\xb3" // U+e073 +#define ICON_FA_USERS_VIEWFINDER "\xee\x96\x95" // U+e595 +#define ICON_FA_UTENSILS "\xef\x8b\xa7" // U+f2e7 +#define ICON_FA_V "V" // U+0056 +#define ICON_FA_VAN_SHUTTLE "\xef\x96\xb6" // U+f5b6 +#define ICON_FA_VAULT "\xee\x8b\x85" // U+e2c5 +#define ICON_FA_VECTOR_SQUARE "\xef\x97\x8b" // U+f5cb +#define ICON_FA_VENUS "\xef\x88\xa1" // U+f221 +#define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226 +#define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228 +#define ICON_FA_VEST "\xee\x82\x85" // U+e085 +#define ICON_FA_VEST_PATCHES "\xee\x82\x86" // U+e086 +#define ICON_FA_VIAL "\xef\x92\x92" // U+f492 +#define ICON_FA_VIAL_CIRCLE_CHECK "\xee\x96\x96" // U+e596 +#define ICON_FA_VIAL_VIRUS "\xee\x96\x97" // U+e597 +#define ICON_FA_VIALS "\xef\x92\x93" // U+f493 +#define ICON_FA_VIDEO "\xef\x80\xbd" // U+f03d +#define ICON_FA_VIDEO_SLASH "\xef\x93\xa2" // U+f4e2 +#define ICON_FA_VIHARA "\xef\x9a\xa7" // U+f6a7 +#define ICON_FA_VIRUS "\xee\x81\xb4" // U+e074 +#define ICON_FA_VIRUS_COVID "\xee\x92\xa8" // U+e4a8 +#define ICON_FA_VIRUS_COVID_SLASH "\xee\x92\xa9" // U+e4a9 +#define ICON_FA_VIRUS_SLASH "\xee\x81\xb5" // U+e075 +#define ICON_FA_VIRUSES "\xee\x81\xb6" // U+e076 +#define ICON_FA_VOICEMAIL "\xef\xa2\x97" // U+f897 +#define ICON_FA_VOLCANO "\xef\x9d\xb0" // U+f770 +#define ICON_FA_VOLLEYBALL "\xef\x91\x9f" // U+f45f +#define ICON_FA_VOLUME_HIGH "\xef\x80\xa8" // U+f028 +#define ICON_FA_VOLUME_LOW "\xef\x80\xa7" // U+f027 +#define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026 +#define ICON_FA_VOLUME_XMARK "\xef\x9a\xa9" // U+f6a9 +#define ICON_FA_VR_CARDBOARD "\xef\x9c\xa9" // U+f729 +#define ICON_FA_W "W" // U+0057 +#define ICON_FA_WALKIE_TALKIE "\xef\xa3\xaf" // U+f8ef +#define ICON_FA_WALLET "\xef\x95\x95" // U+f555 +#define ICON_FA_WAND_MAGIC "\xef\x83\x90" // U+f0d0 +#define ICON_FA_WAND_MAGIC_SPARKLES "\xee\x8b\x8a" // U+e2ca +#define ICON_FA_WAND_SPARKLES "\xef\x9c\xab" // U+f72b +#define ICON_FA_WAREHOUSE "\xef\x92\x94" // U+f494 +#define ICON_FA_WATER "\xef\x9d\xb3" // U+f773 +#define ICON_FA_WATER_LADDER "\xef\x97\x85" // U+f5c5 +#define ICON_FA_WAVE_SQUARE "\xef\xa0\xbe" // U+f83e +#define ICON_FA_WEIGHT_HANGING "\xef\x97\x8d" // U+f5cd +#define ICON_FA_WEIGHT_SCALE "\xef\x92\x96" // U+f496 +#define ICON_FA_WHEAT_AWN "\xee\x8b\x8d" // U+e2cd +#define ICON_FA_WHEAT_AWN_CIRCLE_EXCLAMATION "\xee\x96\x98" // U+e598 +#define ICON_FA_WHEELCHAIR "\xef\x86\x93" // U+f193 +#define ICON_FA_WHEELCHAIR_MOVE "\xee\x8b\x8e" // U+e2ce +#define ICON_FA_WHISKEY_GLASS "\xef\x9e\xa0" // U+f7a0 +#define ICON_FA_WIFI "\xef\x87\xab" // U+f1eb +#define ICON_FA_WIND "\xef\x9c\xae" // U+f72e +#define ICON_FA_WINDOW_MAXIMIZE "\xef\x8b\x90" // U+f2d0 +#define ICON_FA_WINDOW_MINIMIZE "\xef\x8b\x91" // U+f2d1 +#define ICON_FA_WINDOW_RESTORE "\xef\x8b\x92" // U+f2d2 +#define ICON_FA_WINE_BOTTLE "\xef\x9c\xaf" // U+f72f +#define ICON_FA_WINE_GLASS "\xef\x93\xa3" // U+f4e3 +#define ICON_FA_WINE_GLASS_EMPTY "\xef\x97\x8e" // U+f5ce +#define ICON_FA_WON_SIGN "\xef\x85\x99" // U+f159 +#define ICON_FA_WORM "\xee\x96\x99" // U+e599 +#define ICON_FA_WRENCH "\xef\x82\xad" // U+f0ad +#define ICON_FA_X "X" // U+0058 +#define ICON_FA_X_RAY "\xef\x92\x97" // U+f497 +#define ICON_FA_XMARK "\xef\x80\x8d" // U+f00d +#define ICON_FA_XMARKS_LINES "\xee\x96\x9a" // U+e59a +#define ICON_FA_Y "Y" // U+0059 +#define ICON_FA_YEN_SIGN "\xef\x85\x97" // U+f157 +#define ICON_FA_YIN_YANG "\xef\x9a\xad" // U+f6ad +#define ICON_FA_Z "Z" // U+005a diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index e24c1e033..84215d450 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -48,6 +48,7 @@ #include "oslib/resources.h" #include "achievements/achievements.h" #include "gui_achievements.h" +#include "IconsFontAwesome6.h" #if defined(USE_SDL) #include "sdl/sdl.h" #endif @@ -309,6 +310,12 @@ void gui_initFonts() // TODO Linux, iOS, ... #endif + // Font Awesome symbols (added to default font) + data = resource::load("fonts/" FONT_ICON_FILE_NAME_FAS, dataSize); + verify(data != nullptr); + const float symbolFontSize = 21.f * settings.display.uiScale; + static ImWchar faRanges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; + io.Fonts->AddFontFromMemoryTTF(data.release(), dataSize, symbolFontSize, &font_cfg, faRanges); // Large font without Asian glyphs data = resource::load("fonts/Roboto-Regular.ttf", dataSize); verify(data != nullptr); @@ -574,104 +581,159 @@ static bool savestateAllowed() static void gui_display_commands() { - imguiDriver->displayVmus(); + imguiDriver->displayVmus(); + fullScreenWindow(false); + ImGui::SetNextWindowBgAlpha(0.8f); + ImguiStyleVar _{ImGuiStyleVar_WindowBorderSize, 0}; - centerNextWindow(); - ImGui::SetNextWindowSize(ScaledVec2(330, 0)); + ImGui::Begin("##commands", NULL, ImGuiWindowFlags_NoDecoration); + { + ImguiStyleVar _{ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)}; // left aligned - ImGui::Begin("##commands", NULL, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize); + float buttonHeight = 50.f; // not scaled + bool lowH = ImGui::GetContentRegionAvail().y < ((100 + 50 * 6) * settings.display.uiScale + + ImGui::GetStyle().FramePadding.y * 2 + ImGui::GetStyle().ItemSpacing.y * 5); + if (lowH) + { + // Low height available (phone): Put game icon in first column without text + // Button columns in next 2 columns + float emptyW = ImGui::GetContentRegionAvail().x - (100 + 150 * 2) * settings.display.uiScale - ImGui::GetStyle().WindowPadding.x * 2; + ImGui::Columns(3, "buttons", false); + ImGui::SetColumnWidth(0, 100 * settings.display.uiScale + ImGui::GetStyle().FramePadding.x * 2 + emptyW / 3); + bool veryLowH = ImGui::GetContentRegionAvail().y < (50 * 6 * settings.display.uiScale + + ImGui::GetStyle().ItemSpacing.y * 5); + if (veryLowH) + buttonHeight = (ImGui::GetContentRegionAvail().y - ImGui::GetStyle().ItemSpacing.y * 5) + / 6 / settings.display.uiScale; + } + GameMedia game; + game.path = settings.content.path; + game.fileName = settings.content.fileName; + GameBoxart art = boxart.getBoxart(game); + ImguiTexture tex(art.boxartPath); + // TODO use placeholder image if not available + tex.draw(ScaledVec2(100, 100)); - { - if (card_reader::barcodeAvailable()) - { + if (!lowH) + { + ImGui::SameLine(); + ImGui::BeginChild("game_info", ScaledVec2(0, 100.f), ImGuiChildFlags_Border, ImGuiWindowFlags_None); + ImGui::PushFont(largeFont); + ImGui::Text("%s", art.name.c_str()); + ImGui::PopFont(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); + ImGui::TextWrapped("%s", art.fileName.c_str()); + ImGui::PopStyleColor(); + ImGui::EndChild(); + + ImGui::Columns(3, "buttons", false); + ImGui::SetColumnWidth(0, 100.f * settings.display.uiScale + ImGui::GetStyle().ItemSpacing.x); + ImGui::SetColumnWidth(1, 200.f * settings.display.uiScale); + } + ImGui::NextColumn(); + ImguiStyleVar _1{ImGuiStyleVar_FramePadding, ScaledVec2(12.f, 3.f)}; + + // Resume + if (ImGui::Button(ICON_FA_PLAY " Resume", ScaledVec2(150, buttonHeight))) + { + GamepadDevice::load_system_mappings(); + gui_setState(GuiState::Closed); + } + // Cheats + { + DisabledScope _{settings.network.online}; + + if (ImGui::Button(ICON_FA_MASK " Cheats", ScaledVec2(150, buttonHeight)) && !settings.network.online) + gui_setState(GuiState::Cheats); + } + // Achievements + { + DisabledScope _{!achievements::isActive()}; + + if (ImGui::Button(ICON_FA_TROPHY " Achievements", ScaledVec2(150, buttonHeight)) && achievements::isActive()) + gui_setState(GuiState::Achievements); + } + // Insert/Eject Disk + const char *disk_label = libGDR_GetDiscType() == Open ? ICON_FA_COMPACT_DISC " Insert Disk" : ICON_FA_COMPACT_DISC " Eject Disk"; + if (ImGui::Button(disk_label, ScaledVec2(150, buttonHeight))) + { + if (libGDR_GetDiscType() == Open) { + gui_setState(GuiState::SelectDisk); + } + else { + DiscOpenLid(); + gui_setState(GuiState::Closed); + } + } + // Settings + if (ImGui::Button(ICON_FA_GEAR " Settings", ScaledVec2(150, buttonHeight))) + gui_setState(GuiState::Settings); + // Exit + if (ImGui::Button(commandLineStart ? ICON_FA_POWER_OFF " Exit" : ICON_FA_POWER_OFF " Close Game", ScaledVec2(150, buttonHeight))) + gui_stop_game(); + + ImGui::NextColumn(); + { + DisabledScope _{!savestateAllowed()}; + + // Load State + if (ImGui::Button(ICON_FA_CLOCK_ROTATE_LEFT " Load State", ScaledVec2(150, buttonHeight)) && savestateAllowed()) + { + gui_setState(GuiState::Closed); + dc_loadstate(config::SavestateSlot); + } + + // Save State + if (ImGui::Button(ICON_FA_DOWNLOAD " Save State", ScaledVec2(150, buttonHeight)) && savestateAllowed()) + { + gui_setState(GuiState::Closed); + dc_savestate(config::SavestateSlot); + } + + // Slot # + if (ImGui::ArrowButton("##prev-slot", ImGuiDir_Left)) + { + if (config::SavestateSlot == 0) + config::SavestateSlot = 9; + else + config::SavestateSlot--; + SaveSettings(); + } + std::string slot = "Slot " + std::to_string((int)config::SavestateSlot + 1); + float spacingW = (150.f * settings.display.uiScale - ImGui::GetFrameHeight() * 2 - ImGui::CalcTextSize(slot.c_str()).x) / 2; + ImGui::SameLine(0, spacingW); + ImGui::Text("%s", slot.c_str()); + ImGui::SameLine(0, spacingW); + if (ImGui::ArrowButton("##next-slot", ImGuiDir_Right)) + { + if (config::SavestateSlot == 9) + config::SavestateSlot = 0; + else + config::SavestateSlot++; + SaveSettings(); + } + std::string date = dc_getStateUpdateDate(config::SavestateSlot); + { + ImVec4 gray(0.75f, 0.75f, 0.75f, 1.f); + if (date.empty()) + ImGui::TextColored(gray, "Empty"); + else + ImGui::TextColored(gray, "%s", date.c_str()); + } + } + // Barcode + if (card_reader::barcodeAvailable()) + { + ImGui::NewLine(); + ImGui::Text("Barcode Card"); char cardBuf[64] {}; strncpy(cardBuf, card_reader::barcodeGetCard().c_str(), sizeof(cardBuf) - 1); - if (ImGui::InputText("Card", cardBuf, sizeof(cardBuf), ImGuiInputTextFlags_None, nullptr, nullptr)) + if (ImGui::InputText("##barcode", cardBuf, sizeof(cardBuf), ImGuiInputTextFlags_None, nullptr, nullptr)) card_reader::barcodeSetCard(cardBuf); - } - - DisabledScope scope(!savestateAllowed()); - - // Load State - if (ImGui::Button("Load State", ScaledVec2(110, 50)) && savestateAllowed()) - { - gui_setState(GuiState::Closed); - dc_loadstate(config::SavestateSlot); } - ImGui::SameLine(); - // Slot # - std::string slot = "Slot " + std::to_string((int)config::SavestateSlot + 1); - if (ImGui::Button(slot.c_str(), ImVec2(80 * settings.display.uiScale - ImGui::GetStyle().FramePadding.x, 50 * settings.display.uiScale))) - ImGui::OpenPopup("slot_select_popup"); - if (ImGui::BeginPopup("slot_select_popup")) - { - for (int i = 0; i < 10; i++) - if (ImGui::Selectable(std::to_string(i + 1).c_str(), config::SavestateSlot == i, 0, - ImVec2(ImGui::CalcTextSize("Slot 8").x, 0))) { - config::SavestateSlot = i; - SaveSettings(); - } - ImGui::EndPopup(); - } - ImGui::SameLine(); - - // Save State - if (ImGui::Button("Save State", ScaledVec2(110, 50)) && savestateAllowed()) - { - gui_setState(GuiState::Closed); - dc_savestate(config::SavestateSlot); - } - } - - ImGui::Columns(2, "buttons", false); - - // Settings - if (ImGui::Button("Settings", ScaledVec2(150, 50))) - { - gui_setState(GuiState::Settings); + ImGui::Columns(1, nullptr, false); } - ImGui::NextColumn(); - if (ImGui::Button("Resume", ScaledVec2(150, 50))) - { - GamepadDevice::load_system_mappings(); - gui_setState(GuiState::Closed); - } - - ImGui::NextColumn(); - - // Insert/Eject Disk - const char *disk_label = libGDR_GetDiscType() == Open ? "Insert Disk" : "Eject Disk"; - if (ImGui::Button(disk_label, ScaledVec2(150, 50))) - { - if (libGDR_GetDiscType() == Open) - { - gui_setState(GuiState::SelectDisk); - } - else - { - DiscOpenLid(); - gui_setState(GuiState::Closed); - } - } - ImGui::NextColumn(); - - // Cheats - { - DisabledScope scope(settings.network.online); - - if (ImGui::Button("Cheats", ScaledVec2(150, 50)) && !settings.network.online) - gui_setState(GuiState::Cheats); - } - ImGui::Columns(1, nullptr, false); - - // Exit - if (ImGui::Button(commandLineStart ? "Exit" : "Close Game", ScaledVec2(300, 50) - + ImVec2(ImGui::GetStyle().ColumnsMinSpacing + ImGui::GetStyle().FramePadding.x * 2 - 1, 0))) - { - gui_stop_game(); - } - ImGui::End(); } @@ -2929,49 +2991,9 @@ static void gameTooltip(const std::string& tip) } } -static bool getGameImage(const GameBoxart& art, ImTextureID& textureId, bool allowLoad) +static bool gameImageButton(ImguiTexture& texture, const std::string& tooltip, ImVec2 size, const std::string& gameName) { - textureId = ImTextureID{}; - if (art.boxartPath.empty()) - return false; - - // Get the boxart texture. Load it if needed. - textureId = imguiDriver->getTexture(art.boxartPath); - if (textureId == ImTextureID() && allowLoad) - { - int width, height; - u8 *imgData = loadImage(art.boxartPath, width, height); - if (imgData != nullptr) - { - try { - textureId = imguiDriver->updateTextureAndAspectRatio(art.boxartPath, imgData, width, height); - } catch (...) { - // vulkan can throw during resizing - } - free(imgData); - } - return true; - } - return false; -} - -static bool gameImageButton(ImTextureID textureId, const std::string& tooltip, ImVec2 size) -{ - float ar = imguiDriver->getAspectRatio(textureId); - ImVec2 uv0 { 0.f, 0.f }; - ImVec2 uv1 { 1.f, 1.f }; - if (ar > 1) - { - uv0.y = -(ar - 1) / 2; - uv1.y = 1 + (ar - 1) / 2; - } - else if (ar != 0) - { - ar = 1 / ar; - uv0.x = -(ar - 1) / 2; - uv1.x = 1 + (ar - 1) / 2; - } - bool pressed = ImGui::ImageButton("", textureId, size - ImGui::GetStyle().FramePadding * 2, uv0, uv1); + bool pressed = texture.button("", size, gameName); gameTooltip(tooltip); return pressed; @@ -3036,22 +3058,16 @@ static void gui_display_content() ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ScaledVec2(8, 20)); int counter = 0; - int loadedImages = 0; if (gui_state != GuiState::SelectDisk && filter.PassFilter("Dreamcast BIOS")) { ImGui::PushID("bios"); bool pressed; if (config::BoxartDisplayMode) { - ImTextureID textureId{}; GameMedia game; GameBoxart art = boxart.getBoxartAndLoad(game); - if (getGameImage(art, textureId, loadedImages < 10)) - loadedImages++; - if (textureId != ImTextureID()) - pressed = gameImageButton(textureId, "Dreamcast BIOS", responsiveBoxVec2); - else - pressed = ImGui::Button("Dreamcast BIOS", responsiveBoxVec2); + ImguiTexture tex(art.boxartPath); + pressed = gameImageButton(tex, "Dreamcast BIOS", responsiveBoxVec2, "Dreamcast BIOS"); } else { @@ -3084,23 +3100,19 @@ static void gui_display_content() if (filter.PassFilter(gameName.c_str())) { ImGui::PushID(game.path.c_str()); - bool pressed; + bool pressed = false; if (config::BoxartDisplayMode) { if (counter % itemsPerLine != 0) ImGui::SameLine(); counter++; - ImTextureID textureId{}; - // Get the boxart texture. Load it if needed (max 10 per frame). - if (getGameImage(art, textureId, loadedImages < 10)) - loadedImages++; - if (textureId != ImTextureID()) - pressed = gameImageButton(textureId, game.name, responsiveBoxVec2); - else + // Put the image inside a child window so we can detect when it's fully clipped and doesn't need to be loaded + if (ImGui::BeginChild("img", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_None)) { - pressed = ImGui::Button(gameName.c_str(), responsiveBoxVec2); - gameTooltip(game.name); + ImguiTexture tex(art.boxartPath); + pressed = gameImageButton(tex, game.name, responsiveBoxVec2, gameName); } + ImGui::EndChild(); } else { @@ -3381,6 +3393,9 @@ void gui_display_ui() case GuiState::Cheats: gui_cheats(); break; + case GuiState::Achievements: + achievements::achievementList(); + break; default: die("Unknown UI state"); break; diff --git a/core/rend/gui.h b/core/rend/gui.h index 99ffc9b88..d0ef7af84 100644 --- a/core/rend/gui.h +++ b/core/rend/gui.h @@ -63,7 +63,8 @@ enum class GuiState { SelectDisk, Loading, NetworkStart, - Cheats + Cheats, + Achievements, }; extern GuiState gui_state; diff --git a/core/rend/gui_achievements.cpp b/core/rend/gui_achievements.cpp index 97ab1d00e..0efc41c4a 100644 --- a/core/rend/gui_achievements.cpp +++ b/core/rend/gui_achievements.cpp @@ -21,7 +21,10 @@ #include "gui_util.h" #include "imgui_driver.h" #include "stdclass.h" +#include "achievements/achievements.h" +#include "IconsFontAwesome6.h" #include +#include extern ImFont *largeFont; @@ -33,6 +36,7 @@ Notification notifier; static constexpr u64 DISPLAY_TIME = 5000; static constexpr u64 START_ANIM_TIME = 500; static constexpr u64 END_ANIM_TIME = 1000; +static constexpr u64 NEVER_ENDS = 1000000000000; void Notification::notify(Type type, const std::string& image, const std::string& text1, const std::string& text2, const std::string& text3) @@ -47,7 +51,7 @@ void Notification::notify(Type type, const std::string& image, const std::string { // New progress startTime = now; - endTime = 0x1000000000000; // never + endTime = NEVER_ENDS; } } else @@ -61,23 +65,59 @@ void Notification::notify(Type type, const std::string& image, const std::string endTime = startTime + DISPLAY_TIME; } this->type = type; - this->imagePath = image; - this->imageId = {}; + this->image = { image }; text[0] = text1; text[1] = text2; text[2] = text3; } +void Notification::showChallenge(const std::string& image) +{ + std::lock_guard _(mutex); + ImguiTexture texture{ image }; + if (std::find(challenges.begin(), challenges.end(), texture) != challenges.end()) + return; + challenges.push_back(texture); + if (this->type == None) + { + this->type = Challenge; + startTime = getTimeMs(); + endTime = NEVER_ENDS; + } +} + +void Notification::hideChallenge(const std::string& image) +{ + std::lock_guard _(mutex); + auto it = std::find(challenges.begin(), challenges.end(), image); + if (it == challenges.end()) + return; + challenges.erase(it); + if (this->type == Challenge && challenges.empty()) + endTime = getTimeMs(); +} + bool Notification::draw() { std::lock_guard _(mutex); if (type == None) return false; u64 now = getTimeMs(); - if (now > endTime + END_ANIM_TIME) { - // Hide notification - type = None; - return false; + if (now > endTime + END_ANIM_TIME) + { + if (!challenges.empty()) + { + // Show current challenge indicators + type = Challenge; + startTime = getTimeMs(); + endTime = NEVER_ENDS; + } + else + { + // Hide notification + type = None; + return false; + } } if (now > endTime) { @@ -89,83 +129,148 @@ bool Notification::draw() else { ImGui::SetNextWindowBgAlpha(0.5f); } - if (imageId == ImTextureID{}) - getImage(); float y = ImGui::GetIO().DisplaySize.y; if (now - startTime < START_ANIM_TIME) // Slide up y += 80.f * settings.display.uiScale * (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; ImGui::SetNextWindowPos(ImVec2(0, y), ImGuiCond_Always, ImVec2(0.f, 1.f)); // Lower left corner - ImGui::SetNextWindowSizeConstraints(ScaledVec2(80.f, 80.f) + ImVec2(ImGui::GetStyle().WindowPadding.x * 2, 0.f), ImVec2(FLT_MAX, FLT_MAX)); - const float winPaddingX = ImGui::GetStyle().WindowPadding.x; - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{}); - - ImGui::Begin("##achievements", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav - | ImGuiWindowFlags_NoInputs); - const bool hasPic = imageId != ImTextureID{}; - if (ImGui::BeginTable("achievementNotif", hasPic ? 2 : 1, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) + if (type == Challenge) { - if (hasPic) - ImGui::TableSetupColumn("icon", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("text", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - if (hasPic) + ImGui::Begin("##achievement", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav + | ImGuiWindowFlags_NoInputs); + for (const auto& img : challenges) { - ImGui::Image(imageId, ScaledVec2(80.f, 80.f), { 0.f, 0.f }, { 1.f, 1.f }); - ImGui::TableSetColumnIndex(1); + img.draw(ScaledVec2(60.f, 60.f)); + ImGui::SameLine(); } - - float w = largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, -1.f, text[0].c_str()).x; - w = std::max(w, ImGui::CalcTextSize(text[1].c_str()).x); - w = std::max(w, ImGui::CalcTextSize(text[2].c_str()).x) + winPaddingX * 2; - int lines = (int)!text[0].empty() + (int)!text[1].empty() + (int)!text[2].empty(); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{ hasPic ? 0.f : winPaddingX, (3 - lines) * ImGui::GetTextLineHeight() / 2 }); - if (ImGui::BeginChild("##text", ImVec2(w, 0), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_None)) - { - ImGui::PushFont(largeFont); - ImGui::Text("%s", text[0].c_str()); - ImGui::PopFont(); - if (!text[1].empty()) - ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", text[1].c_str()); - if (!text[2].empty()) - ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", text[2].c_str()); - } - ImGui::EndChild(); - ImGui::PopStyleVar(); - - ImGui::EndTable(); + ImGui::End(); + } + else + { + ImGui::SetNextWindowSizeConstraints(ScaledVec2(80.f, 80.f) + ImVec2(ImGui::GetStyle().WindowPadding.x * 2, 0.f), ImVec2(FLT_MAX, FLT_MAX)); + const float winPaddingX = ImGui::GetStyle().WindowPadding.x; + ImguiStyleVar _(ImGuiStyleVar_WindowPadding, ImVec2{}); + + ImGui::Begin("##achievement", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav + | ImGuiWindowFlags_NoInputs); + ImTextureID imageId = image.getId(); + const bool hasPic = imageId != ImTextureID{}; + if (ImGui::BeginTable("achievementNotif", hasPic ? 2 : 1, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) + { + if (hasPic) + ImGui::TableSetupColumn("icon", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("text", ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + if (hasPic) + { + image.draw(ScaledVec2(80.f, 80.f)); + ImGui::TableSetColumnIndex(1); + } + + float w = largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, -1.f, text[0].c_str()).x; + w = std::max(w, ImGui::CalcTextSize(text[1].c_str()).x); + w = std::max(w, ImGui::CalcTextSize(text[2].c_str()).x) + winPaddingX * 2; + int lines = (int)!text[0].empty() + (int)!text[1].empty() + (int)!text[2].empty(); + ImguiStyleVar _(ImGuiStyleVar_WindowPadding, ImVec2{ hasPic ? 0.f : winPaddingX, (3 - lines) * ImGui::GetTextLineHeight() / 2 }); + if (ImGui::BeginChild("##text", ImVec2(w, 0), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_None)) + { + ImGui::PushFont(largeFont); + ImGui::Text("%s", text[0].c_str()); + ImGui::PopFont(); + if (!text[1].empty()) + ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", text[1].c_str()); + if (!text[2].empty()) + ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", text[2].c_str()); + } + ImGui::EndChild(); + ImGui::EndTable(); + } + ImGui::End(); } - ImGui::End(); - ImGui::PopStyleVar(); ImGui::GetStyle().Alpha = 1.f; return true; } -void Notification::getImage() +void achievementList() { - if (imagePath.empty()) - return; + fullScreenWindow(false); + ImguiStyleVar _(ImGuiStyleVar_WindowBorderSize, 0); + + ImGui::Begin("##achievements", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize); - // Get the texture. Load it if needed. - imageId = imguiDriver->getTexture(imagePath); - if (imageId == ImTextureID()) { - int width, height; - u8 *imgData = loadImage(imagePath, width, height); - if (imgData != nullptr) + float w = ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().ItemSpacing.x * 2 - ImGui::GetStyle().WindowPadding.x + - (80.f + 20.f * 2) * settings.display.uiScale; // image width and button frame padding + Game game = getCurrentGame(); + ImguiTexture tex(game.image); + tex.draw(ScaledVec2(80.f, 80.f)); + ImGui::SameLine(); + ImGui::BeginChild("game_info", ImVec2(w, 80.f * settings.display.uiScale), ImGuiChildFlags_None, ImGuiWindowFlags_None); + ImGui::PushFont(largeFont); + ImGui::Text("%s", game.title.c_str()); + ImGui::PopFont(); + std::stringstream ss; + ss << "You have unlocked " << game.unlockedAchievements << " of " << game.totalAchievements + << " achievements and " << game.points << " of " << game.totalPoints << " points."; + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); + ImGui::TextWrapped("%s", ss.str().c_str()); + ImGui::PopStyleColor(); + ImGui::EndChild(); + + ImGui::SameLine(); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); + if (ImGui::Button("Close")) + gui_setState(GuiState::Commands); + } + + if (ImGui::BeginChild(ImGui::GetID("ach_list"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_DragScrolling | ImGuiWindowFlags_NavFlattened)) + { + std::vector achList = getAchievementList(); + int id = 0; + std::string category; + for (const auto& ach : achList) { - try { - imageId = imguiDriver->updateTextureAndAspectRatio(imagePath, imgData, width, height); - } catch (...) { - // vulkan can throw during resizing + if (ach.category != category) + { + category = ach.category; + ImGui::Indent(10 * settings.display.uiScale); + if (category == "Locked" || category == "Active Challenges" || category == "Almost There") + ImGui::Text(ICON_FA_LOCK); + else if (category == "Unlocked" || category == "Recently Unlocked") + ImGui::Text(ICON_FA_LOCK_OPEN); + ImGui::SameLine(); + ImGui::PushFont(largeFont); + ImGui::Text("%s", category.c_str()); + ImGui::PopFont(); + ImGui::Unindent(10 * settings.display.uiScale); } - free(imgData); + ImguiID _("achiev" + std::to_string(id++)); + ImguiTexture tex(ach.image); + tex.draw(ScaledVec2(80.f, 80.f)); + ImGui::SameLine(); + ImGui::BeginChild(ImGui::GetID("ach_item"), ImVec2(0, 0), ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_None); + ImGui::PushFont(largeFont); + ImGui::Text("%s", ach.title.c_str()); + ImGui::PopFont(); + + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); + ImGui::TextWrapped("%s", ach.description.c_str()); + ImGui::TextWrapped("%s", ach.status.c_str()); + ImGui::PopStyleColor(); + + scrollWhenDraggingOnVoid(); + ImGui::EndChild(); } } + scrollWhenDraggingOnVoid(); + windowDragScroll(); + + ImGui::EndChild(); + ImGui::End(); } } // namespace achievements diff --git a/core/rend/gui_achievements.h b/core/rend/gui_achievements.h index 009581f20..733cc1a6c 100644 --- a/core/rend/gui_achievements.h +++ b/core/rend/gui_achievements.h @@ -18,7 +18,9 @@ */ #include "types.h" #include "imgui.h" +#include "gui_util.h" #include +#include namespace achievements { @@ -33,24 +35,27 @@ public: Unlocked, Progress, Mastery, + Challenge, Error }; void notify(Type type, const std::string& image, const std::string& text1, const std::string& text2 = {}, const std::string& text3 = {}); + void showChallenge(const std::string& image); + void hideChallenge(const std::string& image); bool draw(); private: - void getImage(); - u64 startTime = 0; u64 endTime = 0; Type type = Type::None; - std::string imagePath; - ImTextureID imageId {}; + ImguiTexture image; std::string text[3]; std::mutex mutex; + std::vector challenges; }; extern Notification notifier; +void achievementList(); + } diff --git a/core/rend/gui_util.cpp b/core/rend/gui_util.cpp index c836f7b9e..223b5663d 100644 --- a/core/rend/gui_util.cpp +++ b/core/rend/gui_util.cpp @@ -25,11 +25,9 @@ #include "types.h" #include "stdclass.h" #include "oslib/storage.h" +#include "imgui_driver.h" #include "imgui.h" #include "imgui_internal.h" -#define STBI_ONLY_JPEG -#define STBI_ONLY_PNG -#include static std::string select_current_directory = "**home**"; static std::vector subfolders; @@ -697,17 +695,58 @@ void windowDragScroll() } } -u8 *loadImage(const std::string& path, int& width, int& height) +ImTextureID ImguiTexture::getId() const { - FILE *file = nowide::fopen(path.c_str(), "rb"); - if (file == nullptr) - return nullptr; + if (path.empty()) + return {}; + return imguiDriver->getOrLoadTexture(path); +} - int channels; - stbi_set_flip_vertically_on_load(0); - u8 *imgData = stbi_load_from_file(file, &width, &height, &channels, STBI_rgb_alpha); - std::fclose(file); - return imgData; +static void setUV(float ar, ImVec2& uv0, ImVec2& uv1) +{ + uv0 = { 0.f, 0.f }; + uv1 = { 1.f, 1.f }; + if (ar > 1) + { + uv0.y = -(ar - 1) / 2; + uv1.y = 1 + (ar - 1) / 2; + } + else if (ar != 0) + { + ar = 1 / ar; + uv0.x = -(ar - 1) / 2; + uv1.x = 1 + (ar - 1) / 2; + } +} + +void ImguiTexture::draw(const ImVec2& size, const ImVec4& tint_col, const ImVec4& border_col) const +{ + ImTextureID id = getId(); + if (id == ImTextureID{}) + ImGui::Dummy(size); + else + { + float ar = imguiDriver->getAspectRatio(id); + ImVec2 uv0, uv1; + setUV(ar, uv0, uv1); + ImGui::Image(id, size, uv0, uv1, tint_col, border_col); + } +} + +bool ImguiTexture::button(const char* str_id, const ImVec2& image_size, const std::string& title, + const ImVec4& bg_col, const ImVec4& tint_col) const +{ + ImTextureID id = getId(); + if (id == ImTextureID{}) + return ImGui::Button(title.c_str(), image_size); + else + { + float ar = imguiDriver->getAspectRatio(id); + ImVec2 uv0, uv1; + setUV(ar, uv0, uv1); + ImVec2 size = image_size - ImGui::GetStyle().FramePadding * 2; + return ImGui::ImageButton(str_id, id, size, uv0, uv1, bg_col, tint_col); + } } // Custom version of ImGui::BeginListBox that allows passing window flags diff --git a/core/rend/gui_util.h b/core/rend/gui_util.h index fc25f3f52..4c5f15420 100644 --- a/core/rend/gui_util.h +++ b/core/rend/gui_util.h @@ -59,16 +59,6 @@ static inline void centerNextWindow() ImGuiCond_Always, ImVec2(0.5f, 0.5f)); } -static inline bool operator==(const ImVec2& l, const ImVec2& r) -{ - return l.x == r.x && l.y == r.y; -} - -static inline bool operator!=(const ImVec2& l, const ImVec2& r) -{ - return !(l == r); -} - void fullScreenWindow(bool modal); void windowDragScroll(); @@ -130,20 +120,6 @@ struct ScaledVec2 : public ImVec2 inline static ImVec2 min(const ImVec2& l, const ImVec2& r) { return ImVec2(std::min(l.x, r.x), std::min(l.y, r.y)); } -inline static ImVec2 operator+(const ImVec2& l, const ImVec2& r) { - return ImVec2(l.x + r.x, l.y + r.y); -} -inline static ImVec2 operator-(const ImVec2& l, const ImVec2& r) { - return ImVec2(l.x - r.x, l.y - r.y); -} -inline static ImVec2 operator*(const ImVec2& v, float f) { - return ImVec2(v.x * f, v.y * f); -} -inline static ImVec2 operator/(const ImVec2& v, float f) { - return ImVec2(v.x / f, v.y / f); -} - -u8 *loadImage(const std::string& path, int& width, int& height); class DisabledScope { @@ -173,3 +149,69 @@ private: }; bool BeginListBox(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiWindowFlags windowFlags = 0); + +class ImguiID +{ +public: + ImguiID(const std::string& id) + : ImguiID(id.c_str()) {} + ImguiID(const char *id) { + ImGui::PushID(id); + } + ~ImguiID() { + ImGui::PopID(); + } +}; + +class ImguiStyleVar +{ +public: + ImguiStyleVar(ImGuiStyleVar idx, const ImVec2& val) { + ImGui::PushStyleVar(idx, val); + } + ImguiStyleVar(ImGuiStyleVar idx, float val) { + ImGui::PushStyleVar(idx, val); + } + ~ImguiStyleVar() { + ImGui::PopStyleVar(); + } +}; + +class ImguiStyleColor +{ +public: + ImguiStyleColor(ImGuiCol idx, const ImVec4& col) { + ImGui::PushStyleColor(idx, col); + } + ImguiStyleColor(ImGuiCol idx, ImU32 col) { + ImGui::PushStyleColor(idx, col); + } + ~ImguiStyleColor() { + ImGui::PopStyleColor(); + } +}; + +class ImguiTexture +{ +public: + ImguiTexture() = default; + ImguiTexture(const std::string& path) : path(path) {} + + void draw(const ImVec2& size, const ImVec4& tint_col = ImVec4(1, 1, 1, 1), + const ImVec4& border_col = ImVec4(0, 0, 0, 0)) const; + bool button(const char* str_id, const ImVec2& image_size, const std::string& title = {}, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), + const ImVec4& tint_col = ImVec4(1, 1, 1, 1)) const; + + ImTextureID getId() const; + + operator ImTextureID() { + return getId(); + } + + bool operator==(const ImguiTexture& other) const { + return other.path == path; + } + +private: + std::string path; +}; diff --git a/core/rend/imgui_driver.cpp b/core/rend/imgui_driver.cpp new file mode 100644 index 000000000..b9435f468 --- /dev/null +++ b/core/rend/imgui_driver.cpp @@ -0,0 +1,56 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#include "imgui_driver.h" +#define STBI_ONLY_JPEG +#define STBI_ONLY_PNG +#include + +static u8 *loadImage(const std::string& path, int& width, int& height) +{ + FILE *file = nowide::fopen(path.c_str(), "rb"); + if (file == nullptr) + return nullptr; + + int channels; + stbi_set_flip_vertically_on_load(0); + u8 *imgData = stbi_load_from_file(file, &width, &height, &channels, STBI_rgb_alpha); + std::fclose(file); + return imgData; +} + +ImTextureID ImGuiDriver::getOrLoadTexture(const std::string& path) +{ + ImTextureID id = getTexture(path); + if (id == ImTextureID() && textureLoadCount < 10) + { + textureLoadCount++; + int width, height; + u8 *imgData = loadImage(path, width, height); + if (imgData != nullptr) + { + try { + id = updateTextureAndAspectRatio(path, imgData, width, height); + } catch (...) { + // vulkan can throw during resizing + } + free(imgData); + } + } + return id; +} diff --git a/core/rend/imgui_driver.h b/core/rend/imgui_driver.h index 3d17cef1c..a84618e33 100644 --- a/core/rend/imgui_driver.h +++ b/core/rend/imgui_driver.h @@ -36,20 +36,13 @@ public: virtual void displayVmus() {} virtual void displayCrosshairs() {} - virtual void present() = 0; - virtual void setFrameRendered() {} - - virtual ImTextureID getTexture(const std::string& name) = 0; - virtual ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) = 0; - - ImTextureID updateTextureAndAspectRatio(const std::string& name, const u8 *data, int width, int height) - { - ImTextureID textureId = updateTexture(name, data, width, height); - if (textureId != ImTextureID()) - aspectRatios[textureId] = (float)width / height; - return textureId; + void doPresent() { + textureLoadCount = 0; + present(); } + virtual void setFrameRendered() {} + float getAspectRatio(ImTextureID textureId) { auto it = aspectRatios.find(textureId); if (it != aspectRatios.end()) @@ -58,8 +51,25 @@ public: return 1; } + ImTextureID getOrLoadTexture(const std::string& path); + +protected: + virtual ImTextureID getTexture(const std::string& name) = 0; + virtual ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) = 0; + virtual void present() = 0; + private: + ImTextureID updateTextureAndAspectRatio(const std::string& name, const u8 *data, int width, int height) + { + textureLoadCount++; + ImTextureID textureId = updateTexture(name, data, width, height); + if (textureId != ImTextureID()) + aspectRatios[textureId] = (float)width / height; + return textureId; + } + std::unordered_map aspectRatios; + int textureLoadCount = 0; }; extern std::unique_ptr imguiDriver; diff --git a/core/rend/mainui.cpp b/core/rend/mainui.cpp index 8191679e4..7a9a84192 100644 --- a/core/rend/mainui.cpp +++ b/core/rend/mainui.cpp @@ -96,7 +96,7 @@ void mainui_loop() if (imguiDriver == nullptr) forceReinit = true; else - imguiDriver->present(); + imguiDriver->doPresent(); if (config::RendererType != currentRenderer || forceReinit) { diff --git a/fonts/fa-solid-900.ttf.zip b/fonts/fa-solid-900.ttf.zip new file mode 100644 index 0000000000000000000000000000000000000000..449985a452f7a075552bbd399c03416a88655f3e GIT binary patch literal 173936 zcmW(*2Rz%)`%fZhs1?-S6jfVo38i+8(rRlLHA;=zD?wY-CTbH^tBMY-O^TxWwMuJC z+Sn^LiI9JO|JUnt_xilf7={-D4z@d}y6&T3T`6{P?Yqwaf`>HjPfCB%K97G; zNB6G;Y&8WzSrUhZ{1hA3ZAk*F&;iey`;6+jSJo0@1LGCs%vV6N{bnVu?1I7NE>l1OrX>6gc|(%U1N?`dY8Vl9?Y5Tb4Y#{S^2w-$9h)k zNNhvv=3aViqeEj2A+622v&+#46D{~<(gs@&6C-K8ov5A3x_4ys>#nKuyRj;;ZE}rM zV`%*0je{5iM27D#gZgK;Vjlm|)BL@Pj@xIRVY3By4y1LVK0Xd!0|h(wbHCv!N)3A5 zje>av3n>f$4p;v?F*v%7scoLvGPmPBc?tU>k4~2u@MQMW#&j6Up0K4j)s-Rt`s+83 z%a#1u-hU9?FhfA2&v%z}az3p}MkzaqJ(&(*1CZEf$s`gE>MJuP6 zjrPJ7NwK)=ANxyVeA%8k{(YIxpY-tG;1=U>d;Mj8i<0u!5vu=od@RaZ4b03e~ zR?oHA&qTF~99{h1+ky$H%tdl3ek&KKBd5KTE{iq(s^3d2bupO7Tv@%*HyU!kRK7Qo zQ43M^e$R-Gyvu{QcJ?ubGT8T`O^|nuzxy%1eYW~*X?;{~jFar;jOmyxnRg4h51w>8 z9rKI+7%eQ)H0IYhd#+gaaM8L_;#T44uxArb-t)!9y7aU^_mj3H%#RKKQ=~imXgBkR zL7@?t;%Dmka3<>R_%YjO-x;H1bG{UoPXo{|@0EEcjXeJ2S1Sy25r8xG^m8B1gQkw` z3W%w9y>3`4k{uZDlb;^?twi)vsOG->e>Z_o`0p7(b4c z4SclvO4A~QIr_L$RI&<0;=w$aaClHk{O|12&=g?sv}bTUw^IyXhP1{m>C~56``)pM ze(>|&jz-+brEMRNPgxT{zs}k__5HQw8HS@?F&4pm0>TGfRbBP4i4eP)lwAMELsb9;48mJ9Zw(tSK+I292_jVwPeOtY%t&CCH|JQ*~criEG{gJ9Av?_UoBSuTZ2` z)PtDUUjs2ui#*I%9`^YRIsWJH%`0Q&$A(SkXve&cAsqgoFUkK|xTA*KFqjVx0uw)62h;GT@`)J{dr^PzuTpM*;5>uwpPRO@88x- zy6Nynz31I5=<3uUHN_UMa3dx9*NdMue{oFUo#J6aMwBM!NBR=GM&f+j;6uON3Y>Wr z3;z)PW*b)6T33}^k_T#bQhX_(I>EV8#`iE?)xp>2zcca!e*f3}h4fsata;hTXUA*p zRem5Yi1*3MkzJLj5oL|G2n#FH(e$+20oVTBe)+pEWBLnKMl)wFMt`{tiyakzuRas; zYsjc6AN@pm(Cv}qpuW~C>$Aa+u7miXg+#o4z( z9q)tdxvn)ce~MY$8X(VDDwGdAPcmflKCJ3x73b5Cu-8$c-UklVM8m%!!x3MIVSGpa zVxdphV$hZ_=rRE_+;rCe)9Oe0>jPbZFU8n!6+nY5k-@GRDJ(@Nf!0~0r%MoVk>aL% znZSmFC_qy|72miOuB6i7zk#jjuEv|VDCL7n!8YGi6a@YQD1UqWfpZJsk8J)13;-9? z+(E~p6|6YCgw|N6q0@rXY|}j9${*kX3IW%rRa&%LL{D!9cbv3{VPkZGURD~XWc0=ZABiu8kp+=2W_A7Es#+J-%1++f2=U?jQZlQz{dN(3 zd}p8rKWVa|zah2(I+6)V&aN@tc*uR*R2{y>FvUCtnPQ6S3D3Vg#W=;Hc1i8B8Uxph zgug|-jy(%KIz2kQoxLoHXU~P0SQr}_8krlt1D~YKnSOx!o4A^M&i<9J_4511cg=yS z{wsY>ujr^&SW6wxQ{z0*C?pE$r zQE_H*wnKh2tW~uY)~eX5+$z)B{V4LgdH;_kMY;ZA-744w(tA+lQK7<=vQt&2^rPti zN-hrvyS6DUE_8;CH@fe^S9}&;=IU;vv(D%{e<71`j{LlYITCx?H$~8@d=6K6*C;`g+Aa@90CO#7v|w z%~uio!7CqOU#0mT_4+>hN;{X`ncSJSjamE=>el{QXC`q5wgsO7ZUuyDYGpX~$+_iy zmYKP`%q{CNrIgXGShd!K$6C&=J7 zf?zv_JE09aVM1Z>sT`SQ(lfVK@A=@TSJhIA24w%1;$H{-Y~h?|oWJ%|wmCg2cdo~8 zl{tVAl-SZc&$c5KmZ~*!tM#OnrNw66ZXVGh)GB_W9G2YvUtsZ|)>S9k+?&qiMH%Q5r$8e&kksaBIHZ4{P5(mo>5%cO4;Al zeMX`LS?S_dEEDBgcNQWAJ0P@@aX>hbBxao2knPAUWZDZ+XOYP(rqB7F|LGa-@$1N4j8)y#F=6BIpKxS3&xuc#+TrX-4Mj3DTBm(dFZ2< zkI8FX36DNb-|?0G%BhuH@?Gvx*k0_%ywl8Sqb(U`*2E;SKE;3Pf${3Mim#2ZmO;zd zR#Q^G|NbZQ&oHp4ML*gs+D=ru5gNsc5NQ``vd@8Q?I%Vl~kPOj+7~7iYaP8zZ{h1|Kghf+IVUb;dTeH4ZgE@A zqQSVq?5XjNf;1Crqv2Ds#Q$Z zk*e^-vJ9zCl=DcVs&q^yyx-`qDI!kloC#4XzbbQTS}yYDFfHkqA8X~Ds3}(VJ%(a zF4?Z(E|0G7T`#(l{NMSb8#5a1t?!OE-l|T4$&=7)2IC*!Or@xD=5qEk$a6HvM>l=2 za~8tB zRa-e9Is0<*o-0x@hz+oGQ=Dg^h#sgJ(zWJML+wI0^|Pl;@eqGVsqKeInCM4=rh%&;kD))Gp{<54|CFtj}Wq1-0OPXAwM+T zW#7E2F)&({d9M4RPsjkeAerePK9tPZr?$pi&;AeIXV>@OpGshMc$vB(zXUVd^DJ{q zM3udH{Aw+!q&1mK-ApzmeaQ8c$ri zje_bV3-)yy*!_F@@5&pdaZu;cKfAqaWCl5z`d$BBn^V_l_AWLACxR9MaZZ3IQW%|) z@PrD%U8QIh7`OcQsPeP+y^&ML(~@w>4zWd`e@1h!f3N?yIb8i7-|m(0XWFT1z53y- zEI63$S!4%fk=s8>4YP?q3RvQ~&(x?3tP-YwFo-ex0J`d;ky@@+c8y}jBxn?L`G>W* z5zaOIlHhZCoK^sQp8ljqCqQkU7K_xvg|;wtF*jpw@7`z;luI=G3+0ac{P(gtt;I|B zzv1e1%!&Fqz^VEc{aH_eTFE^8LsmK-xfo=F7F~O;dvM){R53Jy+2Yy;c`)r z@ZmSZ4=`~NpvZehM+Ko3J;`8Qq+{4+`KE&DD0&?{8;jE_l; zc`opc*_H64g9?8Dz_1z#yu)+lfkbpg{!I>HEIc6xWdX+8-|4(ZW+B=8~hzWvsf z_Y#J#X^NK*T)un+joujLWWk$6LQ7jDS2R|MzdlcV{`PtHGXub~PkEfwz3$LXp*X8; zcg^VxE=+e0o_}bM+w@a2y|j=v*33)_4N!Zg78VxhOju#uUwE-2(ooTWZ|H5P*qK>t z#~f9fWi9M1$W`8zw0p5hJH@yo+fdtZtD&`Fy&>sIcKhV*)~n?Nu?DjplZG!1uEs`i zTmRNg`zg_rq8+A&1mn`6?zIc}{vz0-;ms^fLz@|P@xP`D0D@IA&Jk z`$nq0u&p}Y89N>W4)Bfm>!SZ^q;?IQZEaD0R<^dK{Q`kSF7YjBQUYo9=*&^3@BBpz_LY2x4Z@))IAk-T^F6|l+-@&)llkN?)|Di3y2D9IX^6=h zxT&-4GfCOm2>rd8)5fRa6Ir$s^ul^p?>zRrkz_Wz`sHkopiKHnstE;eoWiy(&UI$l zW?g6R)LTY1e!UsRzudz4r30jatv|nO4_7<-Qx;{@N4b4o+dZ9QTgWHfEk5!RU_T*lz9ZjoIlBS zDt|Qr>6x3i8$}D?H<@wPVC~MBs~vs4Y-34(St<{AA|6O7EYPr{pnwC!&Y{kBGc)H8 z6Bavi%c+g;3j#d{ioI{~s7K1vXGo0N$NpN3-lRw(PCl}Hidq~@ndcQNmNxg`ohV4T zbszQpezC;;?8^IvzduI)!SFs{fAU!Ndhe(Axy$$Yo_I#wa}ju-GwkN*3$w>u`E+Yb zYzr34*qw_=!uI>GhV8c#2Z%9bWwPtVtqX|@aSAiyC3V5bK$O9MA~1-)GCZYMZLe&< zY|nYm`Cygke@Y-;xlp3eQzXllY+k(yN1^Uit4MDTiB{S_c#lF=4#9>@J>VW+f--|X z1SJL41my>{?5i$IEpsocFNZHL3@s1k4&4|E89GDp<(|k0Sv|e;^d78c{>^;Ve8YT2 z^i5*ZK6?MwGQ{IPv0=Y>-))(8`DCaJB|CI)Sz>u~=;x3#s{0zk=AxnF4OxGtGU%>N z$ZxB2A6=$*A~!1k)E@chZQHKaI&MUVl((5XmN(d*-2J(6vB7&Jb>wx#eRTgw{b+k* zZ$o+m8Ce(^M|yg6=g9QP=ZN>{;gQZp$VS?R?8d?d*Ce)9{vF~@bB+D)yh(VSd|hLW z+ryh$j#~QdPVF}B_I#jDgznj^z!>-@$q8n(wm*YyH&nPalL zz|oI{R;pLFo}z}|v7HPT>aJ?&YUQwV9{?NyB~Ad{S$8@YS{zsx98d@`!K`0>3-HX( zq$~EmDS*4qo}^UfcasLk&tBuOS~vz)9|apf-HK*^2;j=k1Uh(ue*YD?+5}qk2K`=l zygGLIzex6)Qi7@oOUvMM5bhDX*Y{qDtGb3Bd4;+yt^a|0#+R@DNAKX;1H$B9y$*Kp z=n=(uN=S`en_d-{8dILuhoXe=r?e1;Ccv=-#D{gD(ah>)V6=IBkV1O*YmEOuplLiw z$%-QfgUE2J05AKgtD0D0!j#U8t#ov55X+7UhkmxH9bP_9t6S=T<2Q{zJbKkLHHw$k1L?32kMJg<0_uc%t`EGai@xq zKDEzb##R~qdvcJCJ@r_#`1p&W7;{kKMRtr~{GFS2$)>{OCRH=5fiXW@$dIeL4;0lK z=$ncD)yWKD=*rzhVMOGYS4?n}1h+ge)9n2nJoJ<+kem#Dn45i;0=~GOj>yGWcg@Q{ zPgw%UiQo$X{sHltGkl9<{HwsiIgvO7%RmYA^dyj+2ac6Y=Y$MND)5(Pq8HQtDv0ap zm3dLxz!#4E2vI!^x`EqJ_24(h2ou(I=N#SDh-d23IQ0lQJ>c!ZI%x1Ns^hBO+zU5@ zc+iYkSteR6Avy>AJ;BW|{y2=^USbUs1(VbpXc=ra6XsEXTgKDzudJGlb;aXvLT1$h z9mMr$E)L8jyjXB88Q^7g{*xTEuFXiEgcPDD5CLHzgXzu@06Jp zB^4hfQIpAdpk{1UD@VvdcO~J|ptkP_+fa_N0dfh{)@L-s47a9Cv@{-D)6$2k$DB++ zIgERVqsCUcDf?x)XmsN$A@6@VTr$r64|$cUX73&l!jMbRgD@P$C5lUN^ARc8y64Ii zTF5`L43s0afXq%+X_cGV3n_(L{ivg=u;a0yG9X2K&kApaclblu8vS!m(Wc)R#xd;S=x?e4#L z=XleBOzXydBi`K@btgE-7#zoiMCA=`!6yBbc7MGfsa=RvaA53vaJD{!f1dEjC`uRqCLj@PT>d_w z8vJJNMY}j__ojoW-V29cGctMtfFfpNVZrg=!g{xy2|g&{kvVZI%KAng#^1vie8m4v z&4ig3V(hXuCuR+KPj57q%=Re}Hz$Up7Zc}0cBcws*?vu1HM5}t!T6WuPsL7*T~Nm& z5OKRS?dx%I>8aYH|I4HGK{Nt95$6BPiOJx>P~W8p$Ym9er3@(d1}r`hcC$`CE@$`_ zM7+$eU#rNZlqOa!Pa<>c3sDLp9_0tq2mNp=8qLxDG_?|DEIjk%;5XFxc=qzcPeeoM zD8Gv&aCWn5MMDu>WVxO?TZDO@92i2x-TY!5KtJvmm1XHF{aNvp$V~l0T!k9<&yvml zS9$RToc&R?TQttP^{bf+w@s0xgu>X|Fel`4@WHJNFx|s*QPOFOI6VGkSBox)W+6JT z5=_@d{5K5sN?T1iF?FdqNWo@e#unvaSL5^=qW0RM-0r1Z2&p)0!Auq>@dtzi^)B(a z^6t)4HOQ8J>Ur%0(3wIyMHcvw<7^@ii1xp%G*Na{Tp@K?R)(6l%hpLK44T*ri#-)t z;Kh6Xd;#Sq;a{gnC>;Kj#T;G9cGJT)12XJuZjV2)UgKqVIl^Ez7~cUWm;; zV+?1bVVRCsdRneZyD+_f7m`|vVA?H41vaA>?JtlA)q(s#s|CHNW|zzU{01ACI#6G8cCU$Zpk?mW*BJ)>XNRlxnflFd2tjY>UWG0)@CO_g z)Yq*3u{47>*4K>Q+Z9fa{dCkV5pbARpJ9w!NPRm0_JoJSE05PFv_9&=SS;7*9x36 z{}E4p9yC{c18Hn}sd$`z7r)K}5Jft@+8D>ZT7mYc@H7)} zJ>-YUOvsYD4;p4D~)6{!n@ztHi*o@+uUMeo>JCJyqpbEHxUx%75;8NJFKP zit!>U!5CGjnc;AI6+$TC+3#Rx4Oi4GjT=gTMezH59*J|>{XMD9jN`wVG=>C}ZqWXk zBUIh%d~E@0ye)W3h>td%bGOTyYHoH#jeN9WJcw6?fyak~guWRvpw1_uK_3$v%#94J zv^rxKjDypXeNnr#{8E{9Mn>XEFuDT&L(4mG-UU&;ich||7{x0_&e<3}H6xM4vjJnOR-2i8k&rpXW-zCpHXW}v-Hf3g zh5Nuv*h!l%R_h8Xa+8}x=3ZyS;oYnu*G|XpJAjOc{ZOd-THk$MT}}B@!u=dDvv&Fo zGb>$|ezbjR+=|Rvikk*x@{b#4f!piVO^?k?-A_aJtHHENzo)e!;&rKdqkIXmdzw26i%aLy;e~B&4|K+`>$(E|m#U7ZU&Ua+Zv^SgB0H zfN(~JwKJ_e_ned-u%7?y6F7G_?m~-Bu-t0a2$*5g`S|jA#N{Tv@+Mra+hp^e?^Xna zVeP+Ze=2`-iT_VPtc>2F(BPWeB&Do}Ee3J<`~gulJrg^Cwv;;UpMDFbC42jt#;Y53 z3-spSTRUqsMTfJxQI(l26N!pT{owa@^`_j+%a3hoB-ORws)Ji|HSmh)Io+scYlMEm z*SOfH_QD}Q{4xcakN)!Gz4?O2{N6@Aa`4l~PJew3*(83|rv_Si%>vBoH#Ir@H}iDc zD5|MxesA64lYHB);u&IG_((IVWB#1@$8(%e+iQ%ZnXh&K9n?wGU?^Yt#K+#G`869` zeN+Cra80imVI0qJ@o~Es+NVJpQ9b12>~Np+gC^DZwd;c-;QsAK)F`H9+X=!Sjo>5w zR6V@GsXj~B*31-R^u1-C?%bbmEs;U6o2essZYnTcv2~jdYMa{Yhee?u@jpF#4Gz4> z2Xo?s=J3=IfgL(YREJ6H6|>!1t8b@kG~RV##ru-jryTK&W){H z>p%JGY-|O=PWE%uyKAJ|OIX3N$DR-a$>#IB@eGkI+nkWT_U1VCr}~L^zL_=0RXx=| ztE0!Y*ihANt{2s#-JWJ9KQ%h5LkrC5!JBhn;Zo_;2xL%qu1KP0O=eC@yk<>)j>`! z3!p}OQ%nN*&A}X8Qtw6SCxI611!{8qD_k~irSdDnF8-CjFC`9K_<1%xutmVlf;MgL z!Ul3JoN|mcv-)<{oMDWfY1y`yV9l7mctzz$H?97?&{S7Wm58%$2`}`MIqJL>5le3u zugMhX?}gUH4A@JI`Ob$IfjL#8I~+krqk*`P;zO&!G(0shN$m~9c@(b*DKZb(Q3bc} zor`A*ct_`gKe43=Q4KM`*GXi?c#~Zq?#{e$ThK5$O-MxVR#fVVnbo);-pI^K@$0}8 zwa1F|3cr5K*CfJeE!4JYMm_j@V4)DWj2mtb8g@>nnZjcV7}^vp!hy`Tsl+`q@fzF> zyshOJ7pGe+`5~n5WT19lN`8iKpf=h)10>5`9b^Ru%5rz#if7G%Dp!pBdQp|)!>cx;ZY@&JA<3a@gk7uJuE)1zJBNsPXcu4sAi9gkDSkG0<&V%h(# z?$z-sdcZkbm_$cf1oyvtFDCRwLoO=~gV#pD?w2^*#tf`M3Gd-?u{ZQo znFdwaW1wDgk3smb_@#iskL-2gteqv(Lb@tE=TRVXG${BU4NC!X3kK|}FBdXL{;@!g z_^dIqWANb9`j>m^z~bAPOhSIJIFcnGjJJOhyrBV{_gXNn1W^cl=_?)E4J zOnv`13RM|gW=G_p%Mli2{rHzKg*{$m|Bcf>;(6$D_-!Ak%364iD`ZJhfhPwIbj}HZ zs_cIFaRtJ=t5%SUE|<4>G;f(b%yS+MjrPj+9a+oE!LjFfX)*1Oxtma=U!97dIpIiyZEsy!}1mUd!TZZ|Gt|_64AW7BHe5gm)IP4~6iOzAbq` zRZ>$-n)bx|TVBR0LJDs{9#)nk%$=wJ2o>~kml;I);J)Q}b(=KhzPwP1s0K6PA3>X3 zvV8|}jXAc6Nqc>GVYF^MT^pR33EoarkF^HPY)h`sai{PuHT@V6&s+%EH-Ye;_APy& z!WWR;6{lDo=V=a;(d*?drS9aI+yeHisgU|pZ&uK*(Uuo?s32&0$J6ly@2f#oG~+4e zZwNkX6FJ;x)Q6XJxMT)Z(JqR95dZQ*2Qu>vN;alxEPPH@gLE~HM>U#`VVbWx=b)_x z+m|LwU&g{|TZ0%noX!~2^&q_c7>>i7+ULbG1*Rrr3W8OIRCnjIsfVgWKV$fW;UG28 zwYt4Ci>KpNdI8hEbs&~`t9s18TDaRQ^H$XmRYy@9dgC#xKeY)GUbtGDM6)rg9qGdf z=|gKKe87e}M5R)jw$+tE>1Dd2#*0`@+O7)yni5UR_;drCwIUnbZ_V&s4icW@G>zFY zkU`s%fqXA9TIWTq8ZB=H4VMiLVKeoUh?X8hNgwJ6xJ5Ap4RUzBWNy<8sRIkB7xsjg z_Nf2KQD2nSOYBk)X#@-45e)ce4Ci+eqqUBBAeu|>q*FVk*Hko@rlk*urBh`E_BS|4 zD;!JG0-8%4{XaNJHO#zUpgcjOZ=PkbtX=Ad^|Dqt;8BSMypVKEz>&` zkfDWC7C3)0APUi3!==YatgU%!F=wH>1qOP>*U%YSB#E_5w=jNLJtM??;484edC&k4 zbqn7oK*&TVRPD6fiMR09I|;SI>SGt;Vw-8SZ_}6j|I48Tef`3oHAFLfjmrOPYgnzZ z6)oY-4P3QWX%4!gk#PD2T+&74gOIcW=dZ;Dw+T>CXgBk%X$d{&(bMS=P(FzlKeqtN zZ@{gwV}e=Ifk1PazuX+UuxHUx#(;_AZ;IEE(!q@ufMKqK?FT?}*?;+5x^89u@|Vy= zrLImJzyuTjDg>?gvss>1*KfF)i&o(%ke`duWyp~K46JZ|iJyfN6HYrI23GK69T1}( zJa|T{Q1Yu(9_c--?j(vlyxw&l1T;TqPPe<{!W*+12~4H14V*Bz3|R(tI09^7iFikl zaRYu1E{6}7UQr3Ox5YJKch<|OFEVf)71U%)DdW|#Wp~IxD?NxKLCnT4z}SO7?q&|1X#fX zJa8LiJhD26kVDoI2KKl>NmIMq-B3(ZT_8CPkgaoijAam>LHY}^>Ea9h5!QeilKP=6 zD4R$;&ZAXGo89#Xehf6f<%n(!DF0mi3O@j5b;jX^xO79^V6hp1hzkgCpX@|U6QKO~ zF3pPvz$LSx0U=h*sf{{J9JvLECc6UP5_b2}KuH$+dsOV2R{kHa(9=^l(BL2Hw_F$$ zce*8mi>X*T74i+C0RAh&qh(igzusdmNk;nCN0WJg?ukoyEGLE|)ojfYyiYa)4PIad z%Gh;PpD{1ILe@@}!mnUZbkrj&q%9BZf)SW%BY=>gmJ7v7BA0|<7g0d>6C55vRZEVa zB83cR5E5d;aP%xwZ6oyT-2$4A2S%F!);pr3Z2>b7;%Po$KVTW31LCGH;Vy&f9$bnh zJA;y5KKpP7)K5tjXnAHz&gFgA~C%*+0Cg9kmf#&5=-avDpUy&CVMtxVs znia#bo0!ZDG^gn*{|wD!{x13Yl5o3BVyUU{D9aVy{~f;81FB+Z2K2!SN8N$iwt$JQ zRJ<$5xEsfw$mqgL8t-ER8NXg(H)2E+ufqc40OO(T1P)YGGq>tKrV!%h&^)nPP*{?sb5wl0-Krz=Xop ztL&5AOt2 z#Xr5>-u=PCfF^puh$299z4qHrS#`e(z=+mBa}oYUX{0oFQBD{{c#bh~!~%no z{~MkG*w6TvbHX6pnN3AWxSIn;k`v+Kh055kCMvodvFu z9M;azSvh>P3UtK4eHp5kD#D0Kfb0X}(iGLdKW#3tgYwfaG>9m`!;^24Wmhn^J}IkU z1=`@K{Q*$E;dZMDU_vUR4B|rOz^J*spl@x^8K=lx``-1gVV`)h+es)K#@-UPz~wJV0tAsELw7{6=qCxFSer`q@>AhB@We0J2$d`V`D6CUR z(58-|VreK9*%hPH5ixM4zaTcE2~d@AbxZpmVB*_EXsIxA{cv#^l>aJpu2m16qwIb>U<0~GEopUA%k2K1!a?~U9>3KjKvgHpz%WQ$&TuRSW9J0t-ZW zpnLOQEoXKNcQ}6J6`<L5cI>wUDDbkMJ_e!0lY>o`@;2zuE9)Zt z8<}=G4V&L&`F1;i>%4&rNuIaSE5j{Q@q)SEnm=V)JuKf_Mzejv^Odxy`O~{O){I7{ zR=vo)m(tSAy}Yg08oBortJ!Zlb}jFX)0yzF66%S>{xr+_u|CYhq48!6i|!1Ifree9 z=a%qce!`wFlZ))WHu%??YYReskzh2lNO$|M7k@_oXSYnsN6dC0vbmyo6MfXE--fr& zV9z{BUi&U7vhV|G)8Oh0!0K#^_gf4cC|gy1#>kG)g=zAQjsnBrEX3lBj!`2XIo&o! z>juUwtQN9547j>!-G3BLSU?wZiBMAZ&doAc?(oZNz5Mvox>*KUB(pn`>xHe$-0@k8 zV%l%J^6)Tfw|+AKmK548xa)M7%xBfjw>NTTeawQJ8P1%9U2?$2`!B=?ExgiwJ!Y}45-OM1Nj!wV?EeTE zCj zKSV$BTKqrR{&sH54z2;%TW5x$7GEa347#@ddEaOGrXS~-UsI#b#0WIIJ3gp&7vtO~ zS*s#a1Lb3;Eczrd%*PA^9TGh^f^T1w!8_yIN%}3G44PXGiTi>Xq|&5d$-fIZaTPY1 zPXYY@nx6Js(#MYUsg2xp9O*xQX0>{dNukXY#l{^H3`k*mcbr&Ea0`QkM@uo%Z_|Ee z5|)h-v>P#;Dm8U`BR5V*qIMUftjhEzY4CB~T-j%50fc_H=g)*FfQ@MOx0s^1DSPAr z%im6xggRecCn4I!r}`!S1AA_#{Q{?d1Tg20r+*ftdUY`0B$0Vh*2huS6iZOz39!4Y zM@#>S@kbirvQgpf^V7xEu?7}^CkvpAIc?i+%WurrvOZ_tKHyTSUL?733~Kboit^2F94YOTXq1 z*Ns3Vg~fuSDVIh|^_@n6iEz471W4N-)Sy~^9e(RU?5m4?<&FdDbAW|N!Y*VwPM$2C zKf$d;;0S77b6DJ*yl&f>Yzv;S$M5!v0#ql8?VpM58W~m%uldW1cEkV9T)}zCl=|Ir zj)klu3_CAU5Ax!Qx`ju((HUe;ELp4TAZ#8%drG0-IJU$Eb9nHHnLT*;aJ@gSEb!Mm z7SzMbCb3$`3<0SV3;r%zH)&~5c+Dj?eL5|T_w(%!+7ykq&&wBxC<-t$?lz71LMZZ-#bCbvr?k@-Z71n+bcp~)oGb0L;pvp z!zhY%k;&iP)IDE%+$|qE0gwa+$j4I8w7EdBz-0kq)$a8d{?$T{O$GMr{S)?++}MAq z{5@Y0sY1f96-b!>=r8#7imT#OC1r5{^>k?=O*@6AN7`!r0mG@5O=LSTd@+%P%B0*``Z7w$|KuwGxyl;U13<+{ z>=07IjyrRu&vMIyi+`f-o~nk|1p#)S;Rc>p6a=yE1}gv;;oL3S+!6J91%GhSvOA66 z&%da{!5-S~j5f?MucZ^qeG@A>jC@^&vM_hyzm77Lmi;mmmZflqUZ^u}9O*uPM(;th zSa*mqLi<;7(tZgzy7>93Bz6>K^9NwnXp=Ps*i}V_YA~s*Glgg~d1we6=?I*{Xa9|K)oc`(;}2@af))jPkML^;mgs0pw4CL|&Fh7> zrG^4!hNalpd)G(oO7-Amx!nVu)QD!NKL`$au! zVK{p&tAUc!ox0P-?%k~$s7JmKSuep z>0ylFi^-oxde0891Cz91si`-NK=}!A4jZlta*B18V8>_hdSu|@iMEZI)WEOX<_q*8 z_tUlQ(ErH{6tv0jPjfBLv4mW&aH`8ZMh+B2y71Z9RgmifW5%AD>u?hq^#uO>vrl{v z#4YruyKelYG1vJg4%ml5BLtI!4)GE2A<|a`7eNj&8$f-JHS)FG}vVyr9?OWZ^KWSsV2RU*a(mqisekVt4N&^?tVh>+=$vO*~}Dj7e0!d zZx=j@kPnEVnca@Td_M~$&KC&G~jm zjI5{T#?pmoVs|iVI|e1fT(x09mM|K6AtO>$CRP$T8nt(B2{Jly{5EMR>$<@@bmBdK zc6pI_eIo?Ee>$IFey+W-3m7^U?%H#fq9DpC+NobDqUp{zBh=ndo*JBYr=E0y$g!M$ z`j5|Mk?FulT(TvBT(no(Wqx}mU-;3dTHt#>Cx>d+L! z5~i__zDhVDdSKH-dqD!5@D1z0>?$qW@>3Rkw3R zS}DC;Yy#O?mqL;qB56$$PQ3E4G#LoY$wkru+tNoK53r84p4p_V)X#gUbBT^K&uD^2 zDzO{Df}mZbyxi}IZ)!yQQu@LSvtloYEYR`$(qHsjzlJn@6W=eqSx{|tEU?C<6{c`> zcXHeL^x_F-qFd4M+TgP(z{LGxZ?Z>Kmg~KRK^4E}-lTf-XH3e&>N(y|wGS3YRh;1y zdx*;uymIaj z@w`KA~D9>#Pb1m?)HV1(}T|2|!TT^puLFl20xz_R1DuKkd_q|7Z;S_N6rU8l~KDq3^ z6vZ$U?xThynDgmAusq|QuoyV=d|nWGbtCY~q88(_2A{eH{gDnAu`E%kGy_jtGxeh7 z!~*xv(U@l8R@6ykmE&`=oMCL9_F$8lb^lLf_s^_VPAPmYX8bOO_`62tkbCaUIz zHUu*)JQS=mbh;Q=wSXl18|Yv&NHLhvrlQ@Q;hItUEUaueLEmeWg(gqPa5e*7q6!ca zqV0Ox`#Gb0D-2yaw*5s*O<`Lun)0-XFksyrm3hpL`O(?Z#j?!bqXtia?LR1Xd+$>= zaK?hRV0i;$`Ah1y%8&=KINv;1dsFOesL}N*Oi1H0vdOu((;r~;zo>q`uW7)lOLCW7 zOH-hlN{FU!iKGjm$nA$8U#&U^5Oorx5%DB{kX$SSnR0pgyp;}c5u1!}wDL^V*(NGY z#3oRQy^VYYpyPa|vq-G4SAXJNWL?FTwOY2jEn7n|J6$nT@4DPUrVS^{{5oekI@2P3 zhgdcpTZ3u>MiPJmZOZq7zzD$Rlzi#=LP3 zb4WM;#GtS$#N6N8E%I2rYBkf6nfQpjwmsMwKs%w887jCldZHWy>}uY8*YQ;9Y%W`mSyk$6vbe7MJ5OZ$Q(i&(UPa?{&tYy+ewWaYV{GYqUF^3* zn#^O}N&4t}@cgS(D8EScKGfD)Qm7Itq|+We?6rO=mbuC{xH;&VznJ0Q$>gEVKoCj` z%eYlK;MjZEj7VJ{qa5Ms^J9AqtIDX6O{!7}h#TN!(fe81Dp7fF>pE`D_pR2dQ&kzG zLrEf@IEV)=MF2MtB11>aI3_n!1lQ1BLts^R6fI7p%K7lpggo7dJkUvw_Ck)&oy2Jz zMjH?+vE%q+-!biMO1SZfb=RhxD+za}$~i;HAwzQ>&@r#@-yxs%0H1Y#QPz~~>2bYR z9?Rc_48WAoH{#2uRS1soE~aIm&?Eu#S_uAB6mJzYp!5TT>4bMbu0Oq(_Jz+9Uf7krPW z{IdsPz}bU8(X}`LF3=O24cts+$;y2&^Cf2Lk`_M~GQ^Xw1r39HPxYx@UjV0yXPGSS>V$Ptee}AiT!}>D)WoTLh%T0| zrmV%8M9-i+RBz+Vt;~^NXb^vdBhI|&H%riO-UAFRCJZGK#l=0{#bhx>eZ!zy7m9g9 zwn#FSDlQg%#(aiJn||QyPePRueV6p8$#(6 zm3IfI+&*t`QFn7AS2e|#E~-44O2?ByzsnVg*2UAZDroDv`h;KCbeG>3h$Ir>Vndyx zsH(TFs3y~RArLV1SgbCdqAoIwpEmBMtOLNZ+X(o>A>Cz!{9Jdt{lWT%Oj9i8b*ov4 z^_*%L9+%%A@N2qmxLm5s=Zl8v1s`|eZF-?mypY2S=&fcu8a1u$Y*F-kv!zPKLeXl|R8h9sFlxTu7w{W~ zs`@Xw*r%#4w?E)pFB|Axe(T9aeihg>pYS34+;jjvz^m>9SL1=7UOnIg67wI}-+X4Q zre{8S`GA`U$()6Ejq;6r1A{Wis}v+bqtK8z?xFIV^rV+YG>+5?xneJ8vS1tJcQ({s z@%6LbHm?Ld8R2C?zvcsrl7dq2P6+x*J-WB#GKxhS6LeqpZMx5t1q%*5aKQq_Iu3O< zG;rR~&}khxvmOuHCj<0g&`KdrXv~VXB;TP*sTAj_R4X4yx5)0_JdZ1#9s926y2&g< zEEaw;-P)Q;aT5nrmIH~CP)nfQEs8G7;xYtfChBFFq(DM4a=-Qm$=A6=Fu(b zx`1vWQ}fjwZ6WjSkk_MR`TNs~EY^Apli^Uk$HJsLVPn$g4{fygJC*s!gY z;c!DV%6T-}P_|COnT&O6pKzH2UhZv+g0EUvnTGmkTkjwxv69lUJpG2%AO0xnU(c2T z!*SwrawN$yBu5Xhkdla@novFB3E5>#@vb~ugRfiFHP7xBYTHLWU`t-k-DS>ZYwRI? z!TkNLKA~x)c@HD8hh92V^n(g`@q4Ks@TBOGdQrFeZ@Va3yWq|IS}}HC~d2 zWjMllgqLYx@ORSh4L8tzdOqZZI5I?UNPL7@8N9L#e$^!?cF~kVvNl!8CT6|cZA#T9 zbEphWzveZf`LOds(2@%1o2XyRC0c>%XYr_Z!4cYpOsP^~xl!3^RgagcdX?eVsUGTv zzTSL#H!Bsjcc@&*VIz?UAdKz=89R1a9+1OR!vP!;rjiIpJ&*i=T z8g<0($XaWxvA&y|_zJiiqK%DN&Fao3`i5&%73#Oc3hhbOPqCi3lpv@k77dotvXqsj z>}^RLQtFWR1tkM-aMqiirfOtNc2r-VqV)A0CCjpApJlDBoVT_v+h>h=4pC!4V_T0^ z=B|u8U^gzkSgsL62k92&X)@a!^Ip06PGnMf#ezBI8wm_06?qbPxQnGW>P|>4O_l$1+ciKk{tVjktnutl1MPHVMY?=0=+l&9vrvYTr}cG3}(01jE# z&eo|>ugwFiDaV4Ea?B;&P5-{kX^tnsQq{b+=kGBJOeevW0as zS5v996zyHMVrfy_s>*5inLqIT<#=(}EDX?FC(4~Yw^T!YOS}Y{fj;nwh0gk|x{+)F z&?eot-ptPzM8Z3?=$j_?cU&P)E;wM=rz&nr`lAoY{*Wc_JFq{Ujz zYvO)L2q<O4*mOnibjoZ^J2tqPtnE&KJA-{M2H*a zH{yv%2+|tvtvU6jB!6()pL)rBT~#m>l1*4$dO%{P3?5H*0ltU+N7!I5EWU<{i$dWt_S z`dFEaD?2-sks(S($)(NX zdFZ|mQnb;SWlM&3-s7+(lWEwp4CkuWA>Oay#>{hi0U zQz`ec?-XsQ>)HlSGG)B&ZSG{!vjN`=@cZbu@&a$8kx=;^&r>Q&T^UR$37)F_g${$1 zmypYM?fN6Xo_|`{tesY>f_dfwwQ=e;wY>F8x3EkFJRkS~2~IxF-5>dgMLfhkVTt(2 zN6088DadC&L#gP=7aq1q#b-b3mG`_4f5FiX?LxaM7OisBa(OZ0WLcMyY1oTFevfSU zduVnEP2+z5`}ys3pPv8vPTA^r{+gapipL+9x(V5iZ$y8~PqTgT85U#iw1Ji59=E^p zx7RGqnJ#(aHQN*xiG?d)GudM#7UG9VW0)e95hzAo6w^2csj^^;aV1o&rzZt8Tf@gG z=ooUeNj&rju3(l>T+=I(6`}Qn++IN2$@vGPm>V|v%>XIL)_IL2e`a?1@@$)FbvLEA zOv)38WiqjVCk6IJ<_`IaAN0vQ^H&9adTXJgRAnvV7AensO`GpYN!?Z26Xvt?DUz{c zzQ@9J0j196NW!yp0&7Ocu#D*?@he5WglMFc$tkKrm=uL+$ja+poybN?e5}gCmy}90 z_~*b3S*{1F_0O1+tT{i?M%X%&TK8+J+pR`bS!p#Yrt{{&6MfCX2cg%NNKLGjzi${_ zF2l0Y7_Kf$cO6z%@u%5Rwg#Gr>&vnxkkI3{vpkl z3@|yR3{n}$2z|2h6meLhqF&B?KELMz63B7c>*tk6lZuk;7eB)R)Og6!LWd}Wpm)T> zjZ@lXp|4!%3+!j5PKxd>d;pZyX!j;)oMm=2Mfo=E048;j`fsUMs@;WxA?2<_QEKJA zy3`gWACNpYwwU@uuQfVo5EN?D0Fh^d5O_BQUHdQUj(v+}{WCN{vgsfmDu=lEsqCPV z9Y~a%;V8afW`4> zcAz?+*PZA#?da9{1YsYS)(n{mbr3>G!c@E4g^i=CLJ7kojSmo5Nn8iXKY}o(1R|EW z6bUHZ-7V-ayh+GiBuwFy^Gr*(NZQOpA>OPA*NXm3#+>aToCnKfnJEX|?ga0*+%G;? zA5Jt{Vq0S(T>rWCl-2#5EA2g$Ienz3UE!ajuT}==ovgoy(A?wCQZ8j~$`_RpnuUYj zS`Np^%VIh{uldj7lS^HICZKXwr>+r+-ZVP9fldb=4Ntx1lU$~dT-M2!uvP4Eb^;{kODT@LDc46F_D6BnFR1}Hhy5L@@3)nQ_h5VB*a4l zZZ{#;?e3>TD6*a~K(JmOKomADNGFr&1@hNP4WL{gt6hvfHLso&yZU+Z;i&A7p&LB@ zyF-v*-%VM)o5yar;RZUp+wZxZhlc4CKjGnF>y(Eo8+f6kgJvV?cYV#Ag#mf!N#(u# z+w4B}AbX5G!%ycIP}Fx$v+E=Q2_Kjg@q*dTs^{p{37UO$72}C`k+d-SYwaOC7I05= zS`2=Ol;+uDv>-@=shQ1o<~T11vO#4MXKEn@*==zTgqoQRC3*<8Ys~y)Y4+@A`%pJ% zeqU)xZ(ngq?~)=};^=QN@-BkOE$OW-2A#EHMD8h)Tqy?{N~HDW&^-+?J8Ku8I4UoO zT{=y*>F#hy*Xt6xHxzKY;$d&l=kY`3^t#+(w-)qq6$4t=5>26y>I#Q_2~7>*Rj#CC z!rEvA6(j7{LvDAY;tGdU#pQ|y40_`6@laSb!eM_L?>ybz-R<@T!eM7`EL$D+29fDo zQ66^)sN3U588kf- z*0dnXzD_r*)kV^udM7>sh zp)y8!^gGMZZH?y210qAaNM7IcB56_Nv*$&sQ}yXZa?_J)k^G@4y#Enr6A^FU_3vFz zK;Qlj`xN^;`zm`}Xj^+beqAPCBJTkjy9qIRlVbF`W*NOn$MESDuookk7ilyVOhbn> zUYKLHuf!kF;z_p_3PmF>F9x=rZ7_ndh}Yc^4@Sd&U_uRgya}%>=I6Qs?&C5V3p^Cp z+{r*BqiIl=we}`&BtlyVtKf(UuMzhJbBa3|*EFv`9`cE2vFbaOuPs>;=c#m>d~xV^ zFe&6UCCNXHqQpa-X3XM-H{lOPy%BFP=1Rh34O}s}Zp0Ek9m+NekU)JjpM2WfhUHD3}@+ks7Ve{h45rG)gQO0qkt zI8J$g05s1Fa(VfcRPeB=wv|s=kXvA?Dnp;1-*#Q)qhicIxOC}^Jj7{b17$*)t)uG8 zaoyNvDuVT=xJ5uP)SY~sPhs!D7$3H0N)kd#ubm~CZpAW$2QQ5+s=L({NP`1|>y6C% zu4+w#klsV~0x7qbgg%7+1m zevGkjU`0(LLox8b&cOfb*-wdmxx0gk+pPqvXMALi`=mr~qK4Zj+iIhb&*b@c-0>EV zx246K=X}2lr~rQ7GKE?(>`1(c%!}WwErmS$NiaD5dvpeIch>vNfG+VZB?J9C%G%jt zb|JKh53@f~E6-3tQ!{VXxYDX}8Zot`w_9rH!vDjfye{dND9-86Y5r+9O30%d@5&V9 z%k46t;7h-8dPkbegvXE^EW#!tN1k*mB(-|?=k%A8?&1YLxc3eeb4|7y4Ay;58yh%|&I@Nt}82+O=y7xz;*Wb(a<{ zY;JceN5%u5=m$Ky%X>k>?}=V|Xi>g<@Vt5Rj5t^L;<(=%EiYQM$m8yyv?=;xdtFCi zp&Q~rBGKgWG)cRF&o}F_i7G}uBRh9}Q~3b@KJ=#3$f^l`&P}dMBcAN7V%AT1J4+49 z^am35Fge&Mjd@00UAD30CW3`qtQ`Lev5-Hk^waMLl)wj4^@sa?ZeQZd^DKIOn69l! zH)b1-uRkgpYf9(d!!NJg!Y{4d67~7%`GqHizy>^Ogbu*OWBza~ROzPQ^CN+)qlp!U zH{cC_meLLiRdrD)u{N>45l){`IhD6pe#_gHKp-Zb9Ua7I^!P$_;54!Bvhh)3N3t{7 zJJ=xYLH0U$Aq&^+*l5?=6UCWWXa^O{+3r@-K-z0qLX0(^Z{8 zd2{8(##(e$zFjJn>I5y!I0aHZYTn_Y{*1*>=fdsa6E!&9ym|8{Y7zPB{Q2`Gl|!zN ze9X#kcx1#>+d)1zS)UgAyW~*^vbVDVb{@NkUCrJ{`?{5+NyF(6lPX#>+9YR6{zS%B zYZjD#i>5W))a_>>g%&z(!O^f0Y~8UnLAza;oJvfuEtP_*f1WqskHy~+TLZ= zvV({c4=?1rh9IrCq_4Xz$uMo*>9Fo}%(yeMZ_nc9X3iHZSRD`d`a>~)AQM!hap*wQ zb=^s__^z#suRPpIT83tP;tK1|gVvq2b>|9OEYwB6zY+bu4>Ho(>|L~TU^{VQ;jT;z zSZE1Lj%IHQoTh!mWbdqv(%x6n?S@Ge=V}(+LhDS7^I>$LfZi3+K~JK(=JkeR|K1n| z4tn-EyaxT1GOy>$RceHNyZo)rHc-yBs#%_am;XCA&On_Bw4?|si3 zzn8||?cST|a2+?ok;F$KgvYv_9d{(#^66k?$g20jpa1;lm*sL6lDq+)^a$ZwPz*Kh z4-6#(ff|ih;=|H6`q<@?p6+&BSI2U7(nMmc90_qHk%;;H9#6#MsUwWliBrEXmPja6d3x^m&Gp>w z9vv1boMmvAtyvBW-9uJ3KL4J5W_mFPtnS%w(^}P@uPF&?%gi;6#Mh)eXX!J<&GLpW z=g>_xDVfR&37}ByX_uY@(&NCWbw?qoP07$~4EOh%eC2$v&*!V`^3tX7KU^gyYBIHJ zuGW)zxGe0%WPdd0a28!DvC@8u!9641VC_85pwqQ>)Uq*)+EPHqf0?{x0eas$vb1At z$?3M7u=nN*PE|1HoUtmENK>Twyj;PW;gjkP)l_uSob+%(nis@$j(0s!r;> ze|WgtbhpZcq*^L$vOJU#Q#PI;Pvp&c8exu?Q#Y00Y`|aGa!d$WGEzt^4t3dlGs;e7 zL+oWX#$I*kcSI7TP9}Z#4XLFswj4iFann_yRR}dnG;2!I5-4|lJw#rtG1Q8GOGx>{ zQ#k-FKS;VVeN6hKw}~8pmdg)anVu^yxgOFmNIS@x{t>->CF`FwCWySL&n0MyK{u&@ zrXSW~{EHKfpev99Q8vM47u2Y`PNk~EV?i<; zDcqXL5$8M0zrB$EfceFI%{lC&Vg{^g&U4Tcu`EGY5-heLJB}Eh?KLmSfoSp) zLr#EIUoZ_2hB>0;g3P2;Y{h^V$;C1)>E>ADvY%Izsm%PYl%|!#;ch~+Sk)c(`xT9U z-kCD2Y3X_MGpVGiDSm(4PTCy~mxl&`B!4*=Ec?;@YA*PolMX)Al7N<%QPpxZnT#%4 zYxUAgjA%5u_#tP~n^ZNU@OYwSaWWb;F0oRsT|{q|J2LnSyIj6 zHntkvpvG^oAcQ$6?zEe>G+_miCh{{8>u^P+n8V0WzX6WqF%hEj+Hi{7WwJR%!z%56LRI^XP$=HBdiARPyZrvH{a3AC-4hRmjQ)4*t>iiV^BZQ8W5-&m z6KQXHE=3*7d&~0Ol2Hq+`EpAiA&x!!+Uee6)o^FO8|zKT2+%(=E=0 z`1SdjEA9toTWhMZFzD4r;IqVzU?;I#rg`Zmrunv%Y{i!5{c6_wq@?e;)6-UrT@XMy z6G4qqXsw#9P2n;w2!}=6rGqYghF&nO`QgKgMMYV@eBHX`%N3qUOQMzN+Q$^b2i7Oa z$`uSHt)ri8ssxvKRBl8g8V|J7vtYra%#SKcv>a6u1&#Z;xss(dxO8bhWlx_e7t@lo zCUf3a`TRM`b->DAoGbwarH_aozlTROBdWA2c%D+?;rYHGs4u=0&ZOq+y5g7kwt4#^ zwi3F}snhJ3Qs}83t(7{&4YGnm6bu;#$7~{9c)sU~wF~;lnKO|R#`&VdI$0_Ymb>^t zVXx11Sg^iwNqx|rQ;t3M*t0?|pEt}KhKGm6e4U7iQyy{J8oPdY_nAwSRwVOzE+Y2e zZ+z^?q{oN6hKGi1pQ2SgZDI|dYS*)uy^U6bPOD{a(c)_f;rX5v%Kze$>D1y9JrnE%!`e~Dx4T?!Pc-sXpS!N3GhNr|a?J~e63g7-%DrKCYAOHS z9e3RE<4DZycDdx5yLhN__xb0auSFtR@?CIN`oPEyG{1l5t%e8UbtAyt-rjyrUr-DfBj9m+qyLD6 zLT-QX%Qa-mQR(B{y}gH9&hCfyF3itgyrid~bT6p9@0C|xNu&=i1Gvy?@3d&~vrP(dl#JcMOfxPLu?av1Sb#9`;ey^t~(_*Vi+E_7$qB-OVi- zb^Yvq8AG#Wj^w*7%8bnCNImT2)=M3Z7u9jPmK{BFR%uUg=vdn6XhZbiz8*QDT1W}y zY%d$rNz?98!l8z_@Hwr@Dj#^!8xWIht8cFC;=Hm8>~JP>9q)o;#hsn~%9Oo&>UFw+ zq8|EX7twvgRr>Rfe)OZj?628p_UF&m?;zLLUef(3JMB8GuL7CGLUKExa5s`J&ghC zc7a9@E`)awY`ys|cK0z+W5^{N` zyJ%?xV)>!r%x!VWTnmz-sW{U-)tAysM#=2C=omj)x^!v%qDA!wsrH<=yW36YI&TU= zldP+Q?igy~y5rGc$nVp9w{~}Td%oO; z#vh6t;-VXB7+Nt+_8VdGVb>wiAU%Fsd?lKEFyL>!!kJ^Y^JCV$kih2!z~{%=e`92# znufWM$d$89cb1IaYx$W8tabNv7p)}~^sl?PSlqR|(&S=JdtHs;%uOtqtKsPOax%G; zOfDlc-Cc+fkWENT67)!wR;n0x0rfzmKeQXHbIp7fG#f`fh+3=UjD?bMyCEpK-JVd` z?Eyl8T8}#%^0-HNDC4G4;1-(dRcMq}f8fDn)V+7@K{pO6A#^AC-kk zrR?si`lD*Y!>WfzYaM{b;@W?(?Auoa>bvh(G-1iP=0!?ESDMHaPS*dYl<|C z*u@sHKWWNoP$oU(=kor~50M!SRvt2)5iNb*a!xc4rfm5Ku~z45m2$b9$(#s9 zDzG6SmeeG8_~FA3M;4}Ch?mj6`?7-}$(=+q{4r|8MSsQE+7I8p0(NPiei0B^A`%bgcEK7<7k(eLlZGdXQN}S~U|4rQ>3O4$ZN4cbBbk zU&gr|gEk|K5E-8@eAT?|A!G zy-wAdCUyFg#uKszmPyPxI&0$>SabgJpB&HAV;WHRNH{{_CFfm*HpvWI$QdX* z@Wky{!(B4Qa5Y?DHT)wjuC@Zs*dZ31nws?bhkS0gvhWr%rabKltA3B0FQQ&Byk*Ol zdd2Pb>H5{eTvGXS(CzkX>JuIr->yoFdf7paxDl58YssuVW@HU&gG4oINLH*XlTqP% z$jv(|dN-3XBfziv`oJ!@g$ENGl@VdX^-xe3NpM7;MQuKX8x)K$URM{VK#m4id~J)$ z^BU?ouWy)VjhS4aXUW>NOL_u1^V;3u31Gk}iaTWNaE0kLdcDD&J9Ed59lqA=!iCvZ zpJ{I&Q5f1h!%FOMb|$;Z5z9sx;+$*V3)25QWw|>z*G~8Kl*os&<_^8QSBa%NBsU{| zRzwgFnzwTH^%}R{@|;g(fSrxD%Q}Sup=pg_2WO=WhK8)rRpwCg*uKql7_c2E&4ZAk zoj5yVPcY{6WvRMY5q+(_BNESgcMjAHOe^l76*kHeufT+B#EH4^QZtWJ6RN6a#>3fX zLM#nhA*dV&YcPs3XnCpFD%Nl=!SL=WI3MJ!Rkbo%P8W+;9AXzAKJK{V_;n}o>r}NI zJomB}PCDr%arqJrTo=FYAU;fsR4U9tDTerU{JKHZGZi^^kj{lO8NY65#14;Ejo(Ew z)LlYCH2I;hJP@i~=CrMhxoA2nBrK({B-YU<6G?}%DDRRq=}z1< zh;eb}dkQz-i^|uc;;j}{JW^sFdaEu*Z}AV$o~L}eCuM0-L38FuG}3lNy4cp$@j zCV#)%-S3CcAcDYB*j#f-b|8?X5HVrf`?RuSuab9!4BfNF@cE229^D9aP+xq&6OYwd zE^~FUxaR?iMAN}-N9Cn^{!+#Ex2@6b-KIY=$sQ6*=JZ01@B7@Sods_WA0Yc3+vKKN zP0?=^ryr;=N$NEGkg2}*RyPSMj$4~%xJvVNw$m@wy&#EEVLDCi=FTw@#KpQLVuo;tHzQID?awMSSlt^M(je>^?; zj2o4|%QI5Z~xADz+Ow#x)BHl4g2BZ0*Oi_XoaJFAuowbv6ZH)SvU{qmQ<9v^O;WuY_eJ0pgK)3TABX1%DY zBw0NYiDZkBV%83ey3x1V6ne}^m#cjnt!EW{jR61XO2`T|jxo|_T0~4PS4Mb-z@Fz5 zG0FPa0koEvOFKmE&JI$(kWzw}`4+mhRdxck#*=c^_|`({wG`f_oE5UHz1f|>O?jWl ztkZYIitTBNdzE1!@X0C_BUVtfn3w??FypQTDITfQ$z`Zkj$MG@$nlLU}XFKHut@ z`ebErz&TOaFDx}=EHH)V;psOr&biE9pU?3D{uq7F>p{mi%3kl)vniT24n7+#Q-|r) z5}tlN=q%gUyk0wZF^~DGIX)VpbG%(}_N+C{C@5&&ocJ+wh{al4^ZC}+Sgd+>!OZvNd2P(y)z&zhVfCz?dR=T5rc*+gM6?ex~d|Sn{rjp8O+3D-mqAHA&An5RBXzU&ehcggkMHA zHt$-8ZFCQJoSFNIbVI9)%6eM6b6ERwm|k3)1fdzSLY>wqlI0prvPwgT*)nL4{f_d^ zWks<{mP}c;d#4@OGNeeWE#)yh!<<4_q!+iQWr*K-Y!dj*yTlM&td#!i3VoHWVjL*f~<*cFIq_LP|;umX`}w!7zCGwmVh^TfZ2XOK{3PU zB@}%d^))^y;-ZLOev_E-LL!=g)W$Ye5xFW?bBXGbE}pr+=iO(fcjeSMv0lCcjY-s3r$&Q7MYOX=)V^B$kq-q{|H z2NnA@x6jCU0^)~n1UImQz8T%!-2so`6F&pN;5CM(`An_lp04(`RFh#8&LnGPugBx_ zolIvZ)7hnTcBy%wx5R@E%43)Qd@@;0!}x zRK2lG%l>YI0dnT&BQ-LIy?jh8CYv)V;AU?yQ{zi(EgO^3cV+zDKL{GuU)?XsVGOGV zOL3W&nws6;EPtMw81i`;H^hw8SvJFPFAW;z@NV;FvkO0JMFt&-AkTi%Wj-z+f1Gq2 z5if*S&5+4@d7R(eY@A~S932WK6I?33Qty=$0$l9(GRQwobh#$FN$pNavr}oO6ko&D z__1QEmVn>)1yf$F+AneT1qzNta`}Dc?BYZB@pCCPy>;7Frg6~YjQDxgZDMaByl3uY zb(4etp6`gyNdscapzDfNmQP+NHXuVBKuJ!O>3pPvfQ2`KU84^K?F2og1~dr)eVwe>7VH z+|s-!`KrLKV31Y=5Id}FF_n-;VvyDoi>1nSln|D{f!KWLd$gYn`J<48M=UOvtGeo^ zM4X#aO%x2Vcrr&Zt6Ga9Uvi#KBtrcxV08=97k~|jFAuF12G zmM!pfUFU_p2OijaVP{>Mf(515)HS8^t@9plYHF$jH!(VRj^BZc9Pcp5VZ5&L01u52 zA;V92WW+kfyF4^RNBS8|)rBdyW9MZu-i93Ktqs0RX5O*%&2U|+@{)nSSgh_?gLkj) zrHkIx-P|~~E*3Lxu>HdE|Jw~RxIqiv5=UmLm*CVVXNp~E#+gvVFsiHKEOz|}`q3(O1Up-<&6>X2 zBP*GPT+4uQX~y|PpV7t4zwpdNKanPq8vogX6yvNMDK9Z##KMazyQ_$>{z5b_m*q^v zfIOC^z&_%193B;xxz^SmzCnbSS}%5+qe&-w?xM;^DHHLX@{pO=kjQzx)2(?O^Ioq5{K zd;8q_^$`9pUxq(^8*NEpuF$Zit;9W(K$^UI_-B=WB$MUij{E<_{=ur<9~Yu0o?_Eu zoAR3DPM!TXmZ_7B`H*>Vq!4@SxQJ z!-zw4=l3cu)&Gg5v-TQ$Y zljIylZ`o-8Bh`|208-^0)!)^%eDJrAJ@(i@CY|=-_?R!9&I|}c9LbJnXR-@tt;L?!hmO0cpV*5FZI0RrwuHOYS~~06zofG> z6*E~ZtpXQku~cX0lJ!sCci(;ei(2k`>ZzwVf9k%LMg8}QYMUCkISR|Bm9Wg~>$|24 zRAks4$ncgj-O3EK@Qn1O5qjRLX_1p{XHT&oue@>@tvT}gYt32CpXUC&<_>qg!sJRg z{z}krxxcW?g|1_-G5&(vWdy&%Pq$Vx?|-=`vX5KWlzsZUboaaf&t&BK<^FvXO?U4T z@m#*YGv*2HFS3lCAS71Dj;kqCTd;M_-S6MhUb8V;O-ebVZ$*BV6F@zrTl9;N&8bvj z32pZP-sM_q%1vcxgw{MsH)s9*{UXwHrlq^;?`h2bTABAULkZX+&Qbv^Ft=5$O z$gnlPGc+LbKwj4VF(hma+5S+r4Ncf1R@*C0om!M-Vx4}hO6Imdm~|9!)~o^Kfa$j! z8qr7QPGM4J8lfc+p(rR;;GsNu%=}#VNHW9G3{-&%VMX$pDp&b zTk&XA>r3`(l}zmvn&mmcrcKA05_`G!c4e5au_nJBw5Qzc$;{KH&zY4ZI!b$dzqHoM zweq}UtxGlLPf@H%JIfxaRqj_C-zPgG-cN+W2xe{HE@PIp6f>hAG2ufZZ2&DE8GA;= zxTljA{@y5A528l;$+V%=FrgY3Hg50OQyJ+@nK6+pFSqVgC(o>*KQgC3=zOnc&PLi0 zEFJCiIrb2rer5R@kbyjD?~+)DC?0RneMC z!EP|;v1X5+D%~1YSE5+7oa%Yeit^A3{SU9w!iAK++YE{Zf1T%l%jfgDo)0Rzt^})R zPXvRxU_2J$*P17l8~8WQJMX;odFe{d?OvLo4M!6D7{)&GY$+d@_3j-M&$KpcI;#v( z+?ZT2$gic~H8?^3OJ)A8(=(xrcd^;-T64Gt|334v57;rS#r%>yOUU>oc{xou=XKR* zzq$cMK?HDry=yCqwlj8hf$tFH2FOqf9w2=zu7!@yBAVk%QG!r_cA(Ls2e_h7g=uCi64wY%B0EH zJTFTb(Tg?b2g-E-tNo3QRE(^ap);J)SyL08xm^n>uN8(Tn_Wg{3*9ciJ@@<+{~q&0 zqCQ916P@9B3@3RKCiCi6^Ul4a!9S~BDKvf;r%J_wx>yD#tJ#EFZpX*V&biEsJ?>7+ zAGI?-|AeHjh4ZJUf&sNj_4)!q443pl0egFL93!F9yaluEK+QKer#;uIdW&08D6+e| zr6JnoOC@7nJQ7ZQi1MVouWD|!@)TQK_r)vUoNXg)xu4aDu)E4t_(jt~mzo|yW7^Vs zYwfZj)at~PzR(u*dKHSX?XFjB_x^^4PO)t;omf+7LAf!Rb7CJ7W4oIVi3r`4 zi{8*;+GWK$upQEzTO2P&u`B?`dK~eU>Bn#b{G2` z`>K!!<}!yFwO7{GSBp*WC8KBuq$VSfGoPIEqA<6OZI2(-UTsv8VUlt1lMaWiQzf~< zc>xmS<3IGd-Mp!(o4e{?21dG@n)pcd<$*c!d47)jSIlvLz+Io^q9 z_w+38u}rGAXGN}N5s34uW>NK;q@o-CfX}b0#1B9({$g%%efgSm}rL0D;wD$0-?&M|f=T(%y z!ZmlUa$C_WDZ^jdOyz)@V@Zd~2r7 zNbQl|hia`evVG3PTO+cN=3?yiwO918>Rv`;eQ4Hn=9T4~Ab&}QQPF=^@^7;cB=9As z3=<>EjIfDC46A>%n)CQ-o>W-V)k&If3(yw4K4V1JH19}=&-bBK2R*nNG<-i+qe?QW zYW~*j{K^mKXIuRm-w0*PQyB(z;vrsso9Z8-9iQAIUbXT+5mk+Z=GE1aE6qHL&sW*? zEv`bwx`N$lM@e*gUdp+ZRuKy=-qW3;n3-`aGFqxAS6ez}M8#}OE^nAKgelqwNv7gT2BXm+@5QUS*Bhxb@EeaL5>cfy?om~K zIOm-SkD_uVn(T;p47Y;3a(+G#ar;~xIcP2;AiP;|?ujP)cyl@peJehX^QLqPvaF$V zAMG(440$}dqUp2=41JwS!6ELrT#6?eThdgQ3?%YR^>ws)iHr8J)dN43Z<4#IuHqe+ zE0l7pG7hVhb=slh?#m8gCx~@8&RAaZmzbJm$ug&HV)A}axI?HTXQNusJy5gjlP{0OSJKp zVb96k%}v}6wC#vLGr(?TpQzb&q{d4^dQK#&g1}6gCd53e%k@}talv+7m()&w8xc z7iL$&Zd7xN@6mU4+V?G$3nhY0P3>BtsmY%(E$Oa6h`vZ)4*Pg8n9$5r9Zk)FWKepy z?U8-fo(CBg^OHU7@M$}VM;sATBh#E>9kEm`b97B|!CHsxaHsakEVuajOlI00ny|80 z<5q_3djrGPseNx~V|6a9IzLw9?^vX{5pH=OC8{c??Uz$sL_)=uHpztAo9NA%+k85! zopmJbF@wd(Y%Qg~N4iNqxG^q6YZU zx9b$MRlnWz^QQ2y2L%98K(4=o2T^!fIcaO9FYjXH;uVR6YH!mC-7UMqVV61ghf&Sk ztd-(}wwuuxvkBW3)KPm;Rn1V?b^~Agr zO?ldKg65;kGb_WVU7{z4ooJy5kdAuRRqI)uU$2QaNiLZ^5J!5<74?bg_M)~2x8>PU zeObz}vW=(Lu~Oa;9vXpAH?2}A>V~S&C#;6%Jb6tuIV;OQa?fzqOppp^N;BR!(bRg> zl6>Aj!0up7?{gYTVk_PZ(7 zsVSFgy(!6RX$nb6gR_H{Eo*CSZCkeNpz7J8LZRUGhJ%5Cp&7a!&Z(;IkHvhtss{`o zI-ly*j40HBKqBC))6`YTWYW-7U$e*P@ zK)w2PDwQJVOf}~6pW;E_3BKSt;5xiejeNFYEfqJN`s@X?`+J_%-@O&747w&10(_b4NY4 zKAo`FYT0X>Wpv6^YB%c*q|=NQyVlyZ%!*RmE57a9;Mea4Ee-(eB-j3Knsm!_ ziz#WxB63@Ts)=zp#J9&Q!;`fGL>vr10Y>Aye_?CVtx9D|Vd)8^tJJ&WPR^!nhD_6$X zL^7?8S!*PlGcV?HFPAxoQytURNOu{Ia9$#st6|0Ps*(X(r)gzlMF6uzgz8Rp)YJ~L z`l*P%cUZ`t%Flo8wbvBBXRX^(?GJv3c)cAYwKT5qdd+n#PW<0Q zQr`bc3&iH0bL+MIQhJ@6puc5}@QK+_W6>sile)K}5h@#JYi@g1V*|7i`c{9)j-l;r zi%HI&~KwoQbpKW!g8A`IIoz7;L3yafR z-%hk>WcrfCHzZNLxfX)3+-)_{W4jl)-gtuqB3Zu4V=tK8V|mPq1$Q}PsD4pKtFg74 z>CSMq+muX?l937DRgxv8k^j=9ns>9%(b4A^L8zC&ljs8!jr^P$-V%jO6#kM6e9mhcq| zsp0zM9^e|SRjQ(SuzK3RCq37&+oX%FgpPG8dl$Q&eZg8EXInt4xott3gYh&al-jxp{_^C*l1scDYR3=2CI5EG8MnPuM)- zCtSU|an`$)2m1Sg!+weP`imv{F0Pk&=XS)z4Qu2UHkKMF7a)+EoSkidHP`jd`{6n6 z{~BMWkMzrL)yXEWlO+MYuk3nK?CCy_oyIO>A7-pwI)lF6prJG2noyKm%^B8mBk8d# zr&ntx*sRSE3|d?wje;V?NlzciMY^0)BSlbfY;)qRS2&cK#hqS}Xs&BsE)h#M2FQ`f z=L_T$^A{IeJci+EDK1_R&j)Q90-afP4ZWl|>Dku2;d)s*6GZ2b)eM>pW^t(Mk ztZFqwGGSZ8baZ%oUqhs%15cvuiG#{<$@_jUMMk`<%&YGH7=`DL9{){GsQE z5=TF<1)4!#yVbk%qT6X;=$7-m znq;+6VdhFTcS&pBsf_`VVEfTZl=f^kJ2Qk44#i?s2$>$u+Y!aP-HK?v+f+j8Q!+Ag zh(w8E9#f{UAn{fsMpTC9dSb5^lD}qX?s--KYloDuaQ54DWMaLq7ySM*F^^IZJ5YXw z{aDbigv0z}S&Mq7+wE$GfbQ(4I&o{$R8=b03Q{R6kl1WbtS$JY;om+Zt8VpdmUm;~ zgU*sx>#!lIq)42ntsY6dPbtagopIkcIxqNAXEgfC^9>CR{&>vOrYH?0K>?QyiqhtZ z#r^g*J~+#X@3n#x>^deT(kkiWhuH>7KIE3Qa}ZOr43AE;=>KH1WJMNK&{?)Susd3hr*QXHJ{2Lk)ik3X>={Ty7AhG!`x zY4!V7napuxt_x2>TED;%S=DkSFypBv$s1pIHdo~_=X9DSw^>_Uw^qaRA!U0c5~qp>>MCsT~x(fwJKbsM0ohl*`)Sq_c|-XW;AApca_-Je1wmH zjx@4mY@HdAu)2?=pa#Ovo#HG&qmaYF6lPmZ|CTt?lHUgDY;s4YykW_~i_O8aq+nPf z^>WsCgT!TONF1X2MnKathcoJ?Zj!tLbO@gW=gSV2(A5ClaM0`dPCFLA7I8>td>F;_|jH z*6baq8brLeXiUx0x?&pHRLg`XAFgN9s&Y-8ymQ9w$}0`_7Cqz(NLRDcR&D`EWAcZ?a&`(EEHIlKgHTP0} zl8)M#b!J->TaC!+!|r0ynT4qJc@i%Q`Ge_(X=;d_@9|7IL(+UG7Bg4KB88@!3(wAz zNIah}sJ*U9`i}F@Ki@O$Q+AN$813X>r#Dc9+RUeB+beQfK}FWq>cLp;w`AH~?7Z`) zmyNz<(iz7-nXT+Vb~d}I#^1|!8_8LI1lHP3mCPPJi|zf#YIgu2pRa;2^YqOUQrGGb zI<=rPP_J_-sc>w)T(-{tz2 zvpbWcQ!uz+-hMr)?Dy~C>31R#rM%yM)4vjl@Gs9yxwXEfnRbmxCYxL8{r=jk!<^%p z7b%b0Tr15pYlAOO`$jP6+>@bC?6+%C$C(1dbYDp6fF^0AI`?bUtzu@rCP}x0y#KtG zx|6^>FfGaLRhgopq4V~6atE0iEXL}|#A3u(Xx(j1Dvw;pKnF5JcS?y|+chQV$41sG zBJbR2a}0}YSC@#G5&DIIJcAp?X1A8@1{;F>IGIhmbTySDVW*{u*Q|JQs_MZS^Vl>z zndz={_MFOaO&@;1v@nR(Ste$tn~ip{nU*jo7R|)xI5Qr0esfkGki1B0-Rz}dOe`$j z?Jh;qLZe%@{oSS#c7+TnfR^f4d3_a7x)9B-0`i*p|8HCeR2;r^huBebBSq)_!x0(L zajp=uR?o8NrP(b7b7@xWebAddjvzwZ_G0Z1R(HE5OSoVZm8odEQ>uTNL9Tzgk9dgyaGcj zsSGN~b-X^5tUs(i8LFpJa-K*?u74GOrDi5|qyuqM1(o!Fe zHa172%>-8Mt?R~UxPFZ_uf4t+#IowcJRdc;{NB2Ix_jipSksPb?)SO4xLCAooLrQ9 z6T4q0DR-&h=nc)Os5cf%rD8E}G}W9)xX_V^2~?tNw>xw3xXw;_(%3QlAy14~H zs@7;!jh8n4Zf~)6f;y{PyNKmxoS1B+V~t6LGPOj>x?Iy?(Qf`=fB&HP&xfozJ?AEI z(>INy>p?5*uJcdY;WDCsP;A;m#6D`5vk&e?{TG{d|GlX(-zy)Z@*Mc_cv796y{i_@ zikQ3i#GPnP>?!jp25@1`Y)Gq&?vVqLF$-P?*@ynqnB807p*MtF_a6BPy)o?0>XKR%HsgRp$<8W+%Iu8bp^j*zOSaLbPd45kfZJ zRoV3iWq&@%R^D zePna3FV6X(#kmGKkNd8rPvXmMl^6DY;@qL%XT>^bbNu#|s;(sVl(y)a#>`%8R4t=o z`6A(Rpj+vhx2k*9ye_4iH#N`ip5NRgA>w2{!+sBU)>x+dSyE&=n6P~j%@$2gs;BdmxvjyVI!g$ z$%N|GVwrqMb@OCWcSWQ5d^D!($s~8Hp?oH$yOl)JrALe~KWzW~_YeDmu_dlVQgg>b zAw!QRJpn!3=x+~ubL4ql7tOS`1VgS^!kg56jsAv6paC5t9Hjh@UAuN|!e_*mEsGhx z#9Ci8kx*U9jwnxgweZrU&J!IvZS<&hB)Ohk8rHli9_>iF)I=icdt}j~MWI;4*S=p; zOZF>1H$JV$3sGMx%V;d=2riJJ1ph2SQ`ovofX9O8U)B%<^LQ}zz4LK^RP2-^;?qUzmEv)*;2os|=P2!hq|l;HSA;yz2g z-ldS>Y)#`#GNMMi%UhZcH0l0 znh|-;sYOVQsk)C?HdQs{3R7>%)WEBj-{~m|ubX0K9(yN_cUuUqHhU`AqeLRj(nvBF zVjT^KFyH%FlcHbzl z5?jlTXBV)m*@wvU{B?Rpb+@$tm0nS@tnfX>NUUP%Vw5|AWzAZ_1uVl!aJQ(6~4%cI^p;!Rz>qvGgdpEnD-Olc3&%RD;KtB@+BsxP{P=ZwXzXBF1=YQ(Ms#w>J(IEBGh zc<3gJ0e4%OTF39?ayjwH>v!2FZ+Zxe?ZwLCMw+;d-W85$Z?<(#yph$w<0oVY9Z2KBW!qMxPus!^KE zxM3m!NXm>!(N}WlV)OEZkVLZN@$QK}HsEOcSra_q@mvw)-0y2@?(=y1nwuz;VepD` zPd)Y2#mR(H)x16xLHe|tbG~5d()rq+>R*FH8zC@M&24exzQPq{eSRv6rvzE!z{4y9P;aZ zY$J5rU$CugifcT|TV~l~;Q!RhCF9b|A_(hm!iuhCS%VWZ?-;^dY|ToDrDVCDobwt9 zrY9*mHRV*zmX`(RqiGZ3Z?*Xh#85=&n(Jp!M75GXVaIv(XGWCa>ZTRbrr!Tk*0z3U z$p)(>TVFjbBZ*z90cZ6b_eYiFxm0fal###O$wTf%Z(WG}A^v6VWxbyn?L?TmK^1ED zLr(QM_eB;uyCa^7SoeWrJ`(hM)}ImPJbX^C;R}S$%DN&ESN5z>z-RQHL-)_G*Sx;q z!L4*Z^I875#~+M*HZwhy^X=AyY1{ew^XdK>>zUZ&tPXtd&lrzBRww2@&tey`L3?jm z!GvT8Z?fc4aSIe1%o1oEYC(3|wIspL6)-8s&MWeiSHrX9jjY`A+nfyGX{+0ua_g

%F%CnB;j`q-z>0l@nOy6K-oVL63S~ElZAb;~) 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 Date: Sat, 4 May 2024 13:51:59 +0200 Subject: [PATCH 55/86] achievements: fix crash when loading image cache Fixes MINIDUMP-312, MINIDUMP-313 Issue #761 --- core/achievements/achievements.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp index 19cb7781d..3d2d12eed 100644 --- a/core/achievements/achievements.cpp +++ b/core/achievements/achievements.cpp @@ -249,7 +249,7 @@ void Achievements::loadCache() std::lock_guard _(cacheMutex); cacheMap[v] = name; } - closedir(dir); + flycast::closedir(dir); } } From 6de2f160a1e805b54c5a7884cf14c2673aaa67d4 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 4 May 2024 17:38:42 +0200 Subject: [PATCH 56/86] achievements: hardcore mode --- core/achievements/achievements.cpp | 87 ++++++++++++++++++------------ core/achievements/achievements.h | 6 +++ core/cheats.cpp | 11 ++-- core/emulator.cpp | 15 +++++- core/lua/lua.cpp | 2 +- core/nullDC.cpp | 2 + core/rend/gui.cpp | 54 +++++++++++-------- core/rend/gui_achievements.cpp | 4 ++ core/types.h | 2 +- 9 files changed, 121 insertions(+), 62 deletions(-) diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp index 3d2d12eed..ecbd6b0d6 100644 --- a/core/achievements/achievements.cpp +++ b/core/achievements/achievements.cpp @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include namespace achievements @@ -70,7 +70,7 @@ private: void resumeGame(); void loadCache(); std::string getOrDownloadImage(const char *url); - std::tuple getCachedImage(const char *url); + std::pair getCachedImage(const char *url); void diskChange(); static void clientLoginWithTokenCallback(int result, const char *error_message, rc_client_t *client, void *userdata); @@ -105,57 +105,46 @@ private: std::future asyncImageDownload; }; -bool init() -{ +bool init() { return Achievements::Instance().init(); } -void term() -{ +void term() { Achievements::Instance().term(); } -std::future login(const char *username, const char *password) -{ +std::future login(const char *username, const char *password) { return Achievements::Instance().login(username, password); } -void logout() -{ +void logout() { Achievements::Instance().logout(); } -bool isLoggedOn() -{ +bool isLoggedOn() { return Achievements::Instance().isLoggedOn(); } -bool isActive() -{ +bool isActive() { return Achievements::Instance().isActive(); } -Game getCurrentGame() -{ +Game getCurrentGame() { return Achievements::Instance().getCurrentGame(); } -std::vector getAchievementList() -{ +std::vector getAchievementList() { return Achievements::Instance().getAchievementList(); } -void serialize(Serializer& ser) -{ +void serialize(Serializer& ser) { Achievements::Instance().serialize(ser); } -void deserialize(Deserializer& deser) -{ +void deserialize(Deserializer& deser) { Achievements::Instance().deserialize(deser); } -Achievements& Achievements::Instance() -{ +Achievements& Achievements::Instance() { static Achievements instance; return instance; } @@ -190,9 +179,8 @@ bool Achievements::init() return false; rc_client_set_event_handler(rc_client, clientEventHandler); - - //TODO rc_client_set_hardcore_enabled(rc_client, 0); + // TODO Expose these settings? //rc_client_set_encore_mode_enabled(rc_client, 0); //rc_client_set_unofficial_enabled(rc_client, 0); //rc_client_set_spectator_mode_enabled(rc_client, 0); @@ -257,19 +245,19 @@ static u64 hashUrl(const char *url) { return XXH64(url, strlen(url), 13); } -std::tuple Achievements::getCachedImage(const char *url) +std::pair Achievements::getCachedImage(const char *url) { u64 hash = hashUrl(url); std::lock_guard _(cacheMutex); auto it = cacheMap.find(hash); if (it != cacheMap.end()) { - return make_tuple(cachePath + it->second, true); + return std::make_pair(cachePath + it->second, true); } else { std::stringstream stream; stream << std::hex << hash << ".png"; - return make_tuple(cachePath + stream.str(), false); + return std::make_pair(cachePath + stream.str(), false); } } @@ -473,6 +461,12 @@ static void handleServerError(const rc_client_server_error_t* error) notifier.notify(Notification::Error, "", error->api, error->error_message); } +static void notifyError(const std::string& msg) +{ + WARN_LOG(COMMON, "RA error: %s", msg.c_str()); + notifier.notify(Notification::Error, "", msg); +} + void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_t* client) { Achievements *achievements = (Achievements *)rc_client_get_userdata(client); @@ -521,9 +515,15 @@ void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_ case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW: case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE: case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE: - case RC_CLIENT_EVENT_DISCONNECTED: - case RC_CLIENT_EVENT_RECONNECTED: */ + case RC_CLIENT_EVENT_DISCONNECTED: + notifyError("RetroAchievements disconnected"); + break; + + case RC_CLIENT_EVENT_RECONNECTED: + notifyError("RetroAchievements reconnected"); + break; + default: WARN_LOG(COMMON, "RA: Unhandled event: %u", event->type); break; @@ -532,6 +532,7 @@ void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_ void Achievements::handleResetEvent(const rc_client_event_t *event) { + // This never seems to be called, probably because hardcore mode is enabled before starting the game. INFO_LOG(COMMON, "RA: Resetting runtime due to reset event"); rc_client_reset(rc_client); } @@ -722,7 +723,14 @@ void Achievements::resumeGame() if (asyncServerCall.valid()) asyncServerCall.get(); if (config::EnableAchievements) + { loadGame(); + if (settings.raHardcoreMode && !config::AchievementsHardcoreMode) + { + settings.raHardcoreMode = false; + rc_client_set_hardcore_enabled(rc_client, 0); + } + } else term(); } @@ -773,6 +781,8 @@ void Achievements::loadGame() std::string gameHash = getGameHash(); if (!gameHash.empty()) { + // settings.raHardcoreMode is set before enabling cheats and loading the initial savestate + rc_client_set_hardcore_enabled(rc_client, settings.raHardcoreMode); rc_client_begin_load_game(rc_client, gameHash.c_str(), [](int result, const char *error_message, rc_client_t *client, void *userdata) { ((Achievements *)userdata)->gameLoaded(result, error_message); }, this); @@ -780,6 +790,7 @@ void Achievements::loadGame() else { INFO_LOG(COMMON, "RA: empty hash. Aborting load"); loadingGame = false; + settings.raHardcoreMode = false; } } @@ -796,6 +807,7 @@ void Achievements::gameLoaded(int result, const char *errorMessage) } else WARN_LOG(COMMON, "RA Loading game failed: %s", errorMessage); + settings.raHardcoreMode = false; loadingGame = false; return; } @@ -803,6 +815,7 @@ void Achievements::gameLoaded(int result, const char *errorMessage) if (info == nullptr) { WARN_LOG(COMMON, "RA: rc_client_get_game_info() returned NULL"); + settings.raHardcoreMode = false; loadingGame = false; return; } @@ -811,6 +824,10 @@ void Achievements::gameLoaded(int result, const char *errorMessage) EventManager::listen(Event::VBlank, emuEventCallback, this); NOTICE_LOG(COMMON, "RA: game %d loaded: %s, achievements %d leaderboards %d rich presence %d", info->id, info->title, rc_client_has_achievements(rc_client), rc_client_has_leaderboards(rc_client), rc_client_has_rich_presence(rc_client)); + if (!rc_client_has_achievements(rc_client)) + settings.raHardcoreMode = false; + else + settings.raHardcoreMode = (bool)rc_client_get_hardcore_enabled(rc_client); std::string image; char url[512]; if (rc_client_game_get_image_url(info, url, sizeof(url)) == RC_OK) @@ -819,7 +836,8 @@ void Achievements::gameLoaded(int result, const char *errorMessage) rc_client_get_user_game_summary(rc_client, &summary); std::string text = "You have " + std::to_string(summary.num_unlocked_achievements) + " of " + std::to_string(summary.num_core_achievements) + " achievements unlocked"; - notifier.notify(Notification::Login, image, info->title, text); + std::string text2 = settings.raHardcoreMode ? "Hardcore Mode" : ""; + notifier.notify(Notification::Login, image, info->title, text, text2); } void Achievements::unloadGame() @@ -833,6 +851,7 @@ void Achievements::unloadGame() asyncServerCall.get(); EventManager::unlisten(Event::VBlank, emuEventCallback, this); rc_client_unload_game(rc_client); + settings.raHardcoreMode = false; } void Achievements::diskChange() @@ -846,10 +865,12 @@ void Achievements::diskChange() } rc_client_begin_change_media_from_hash(rc_client, hash.c_str(), [](int result, const char *errorMessage, rc_client_t *client, void *userdata) { if (result == RC_HARDCORE_DISABLED) { - notifier.notify(Notification::Login, "", "Hardcore disabled", "Unrecognized media inserted"); + settings.raHardcoreMode = false; + notifier.notify(Notification::Login, "", "Hardcore mode disabled", "Unrecognized media inserted"); } else if (result != RC_OK) { + settings.raHardcoreMode = false; if (errorMessage == nullptr) errorMessage = rc_error_str(result); notifier.notify(Notification::Login, "", "Media change failed", errorMessage); diff --git a/core/achievements/achievements.h b/core/achievements/achievements.h index 8b72df131..f716c87b3 100644 --- a/core/achievements/achievements.h +++ b/core/achievements/achievements.h @@ -54,6 +54,12 @@ bool isActive(); Game getCurrentGame(); std::vector getAchievementList(); +#else + +static inline bool isActive() { + return false; +} + #endif void serialize(Serializer& ser); diff --git a/core/cheats.cpp b/core/cheats.cpp index 6b17e1f84..6e9d51787 100644 --- a/core/cheats.cpp +++ b/core/cheats.cpp @@ -431,9 +431,12 @@ void CheatManager::reset(const std::string& gameId) setActive(false); this->gameId = gameId; #ifndef LIBRETRO - std::string cheatFile = cfgLoadStr("cheats", gameId, ""); - if (!cheatFile.empty()) - loadCheatFile(cheatFile); + if (!settings.raHardcoreMode) + { + std::string cheatFile = cfgLoadStr("cheats", gameId, ""); + if (!cheatFile.empty()) + loadCheatFile(cheatFile); + } #endif size_t cheatCount = cheats.size(); if (gameId == "Fixed BOOT strapper") // Extreme Hunting 2 @@ -491,7 +494,7 @@ void CheatManager::reset(const std::string& gameId) if (cheats.size() > cheatCount) setActive(true); } - if (config::WidescreenGameHacks) + if (config::WidescreenGameHacks && !settings.raHardcoreMode) { if (settings.platform.isConsole()) { diff --git a/core/emulator.cpp b/core/emulator.cpp index bfdd7a219..7fa51ff9d 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -530,6 +530,14 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) // Must be done after the maple devices are created and EEPROM is accessible naomi_cart_ConfigureEEPROM(); } +#ifdef USE_RACHIEVEMENTS + // RA probably isn't expecting to travel back in the past so disable it + if (config::GGPOEnable) + config::EnableAchievements.override(false); + // Hardcore mode disables all cheats, under/overclocking, load state, lua and forces dynarec on + settings.raHardcoreMode = config::EnableAchievements && config::AchievementsHardcoreMode + && !NaomiNetworkSupported(); +#endif cheatManager.reset(settings.content.gameId); if (cheatManager.isWidescreen()) { @@ -712,8 +720,13 @@ void loadGameSpecificSettings() // Reload per-game settings config::Settings::instance().load(true); - if (config::GGPOEnable) + if (config::GGPOEnable || settings.raHardcoreMode) config::Sh4Clock.override(200); + if (settings.raHardcoreMode) + { + config::WidescreenGameHacks.override(false); + config::DynarecEnabled.override(true); + } } void Emulator::step() diff --git a/core/lua/lua.cpp b/core/lua/lua.cpp index 7ad7e5ce3..2957886c4 100644 --- a/core/lua/lua.cpp +++ b/core/lua/lua.cpp @@ -43,7 +43,7 @@ using lock_guard = std::lock_guard; static void emuEventCallback(Event event, void *) { - if (L == nullptr) + if (L == nullptr || settings.raHardcoreMode) return; lock_guard lock(mutex); try { diff --git a/core/nullDC.cpp b/core/nullDC.cpp index da3379f29..f3bab50e2 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -153,6 +153,8 @@ void dc_savestate(int index) void dc_loadstate(int index) { + if (settings.raHardcoreMode) + return; u32 total_size = 0; FILE *f = nullptr; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 84215d450..befe01e6c 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -641,7 +641,7 @@ static void gui_display_commands() } // Cheats { - DisabledScope _{settings.network.online}; + DisabledScope _{settings.network.online || settings.raHardcoreMode}; if (ImGui::Button(ICON_FA_MASK " Cheats", ScaledVec2(150, buttonHeight)) && !settings.network.online) gui_setState(GuiState::Cheats); @@ -676,11 +676,14 @@ static void gui_display_commands() { DisabledScope _{!savestateAllowed()}; - // Load State - if (ImGui::Button(ICON_FA_CLOCK_ROTATE_LEFT " Load State", ScaledVec2(150, buttonHeight)) && savestateAllowed()) { - gui_setState(GuiState::Closed); - dc_loadstate(config::SavestateSlot); + DisabledScope _{settings.raHardcoreMode}; + // Load State + if (ImGui::Button(ICON_FA_CLOCK_ROTATE_LEFT " Load State", ScaledVec2(150, buttonHeight)) && savestateAllowed()) + { + gui_setState(GuiState::Closed); + dc_loadstate(config::SavestateSlot); + } } // Save State @@ -1813,10 +1816,13 @@ static void gui_display_settings() #if USE_DISCORD OptionCheckbox("Discord Presence", config::DiscordPresence, "Show which game you are playing on Discord"); #endif +#ifdef USE_RACHIEVEMENTS OptionCheckbox("Enable RetroAchievements", config::EnableAchievements, "Track your game achievements using RetroAchievements.org"); { DisabledScope _(!config::EnableAchievements); ImGui::Indent(); + OptionCheckbox("Hardcore Mode", config::AchievementsHardcoreMode, + "Enable RetroAchievements hardcore mode. Using cheats and loading a state are not allowed in this mode."); ImGui::InputText("Username", &config::AchievementsUserName.get(), achievements::isLoggedOn() ? ImGuiInputTextFlags_ReadOnly : ImGuiInputTextFlags_None, nullptr, nullptr); if (config::EnableAchievements) @@ -1858,6 +1864,7 @@ static void gui_display_settings() } ImGui::Unindent(); } +#endif ImGui::PopStyleVar(); ImGui::EndTabItem(); } @@ -3394,8 +3401,10 @@ void gui_display_ui() gui_cheats(); break; case GuiState::Achievements: +#ifdef USE_RACHIEVEMENTS achievements::achievementList(); break; +#endif default: die("Unknown UI state"); break; @@ -3439,12 +3448,13 @@ void gui_display_osd() if (message.empty()) message = getFPSNotification(); -// if (!message.empty() || config::FloatVMUs || crosshairsNeeded() || (ggpo::active() && config::NetworkStats)) - { - gui_newFrame(); - ImGui::NewFrame(); + gui_newFrame(); + ImGui::NewFrame(); - if (!achievements::notifier.draw() && !message.empty()) +#ifdef USE_RACHIEVEMENTS + if (!achievements::notifier.draw()) +#endif + if (!message.empty()) { ImGui::SetNextWindowBgAlpha(0); ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetIO().DisplaySize.y), ImGuiCond_Always, ImVec2(0.f, 1.f)); // Lower left corner @@ -3456,20 +3466,20 @@ void gui_display_osd() ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", message.c_str()); ImGui::End(); } - imguiDriver->displayCrosshairs(); - if (config::FloatVMUs) - imguiDriver->displayVmus(); -// gui_plot_render_time(settings.display.width, settings.display.height); - if (ggpo::active()) - { - if (config::NetworkStats) - ggpo::displayStats(); - chat.display(); - } + imguiDriver->displayCrosshairs(); + if (config::FloatVMUs) + imguiDriver->displayVmus(); + + if (ggpo::active()) + { + if (config::NetworkStats) + ggpo::displayStats(); + chat.display(); + } + if (!settings.raHardcoreMode) lua::overlay(); - gui_endFrame(gui_is_open()); - } + gui_endFrame(gui_is_open()); } void gui_display_profiler() diff --git a/core/rend/gui_achievements.cpp b/core/rend/gui_achievements.cpp index 0efc41c4a..8facd22ac 100644 --- a/core/rend/gui_achievements.cpp +++ b/core/rend/gui_achievements.cpp @@ -16,6 +16,7 @@ You should have received a copy of the GNU General Public License along with Flycast. If not, see . */ +#ifdef USE_RACHIEVEMENTS #include "gui_achievements.h" #include "gui.h" #include "gui_util.h" @@ -219,6 +220,8 @@ void achievementList() ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); ImGui::TextWrapped("%s", ss.str().c_str()); ImGui::PopStyleColor(); + if (settings.raHardcoreMode) + ImGui::Text("Hardcore Mode"); ImGui::EndChild(); ImGui::SameLine(); @@ -274,3 +277,4 @@ void achievementList() } } // namespace achievements +#endif // USE_RACHIEVEMENTS diff --git a/core/types.h b/core/types.h index c461a3891..e518762a0 100644 --- a/core/types.h +++ b/core/types.h @@ -210,7 +210,7 @@ struct settings_t int drivingSimSlave; } naomi; - bool disableRenderer; + bool raHardcoreMode; }; extern settings_t settings; From 43ba8953a2685a7d9fc860377c05a72cbfea647b Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 4 May 2024 19:24:02 +0200 Subject: [PATCH 57/86] vk: don't delete the ImGui driver when recreating the swap chain It might be drawing at this point so just reset it. --- core/rend/vulkan/vulkan_context.cpp | 10 ++++++++-- core/rend/vulkan/vulkan_driver.h | 16 +++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index b7c19072b..846cc4c2a 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -292,8 +292,14 @@ bool VulkanContext::InitInstance(const char** extensions, uint32_t extensions_co void VulkanContext::InitImgui() { - imguiDriver.reset(); - imguiDriver = std::unique_ptr(new VulkanDriver()); + VulkanDriver *vkDriver = dynamic_cast(imguiDriver.get()); + if (vkDriver == nullptr) { + imguiDriver.reset(); + imguiDriver = std::unique_ptr(new VulkanDriver()); + } + else { + vkDriver->reset(); + } ImGui_ImplVulkan_InitInfo initInfo{}; initInfo.Instance = (VkInstance)*instance; initInfo.PhysicalDevice = (VkPhysicalDevice)physicalDevice; diff --git a/core/rend/vulkan/vulkan_driver.h b/core/rend/vulkan/vulkan_driver.h index ea4dd5b71..fcd726b5c 100644 --- a/core/rend/vulkan/vulkan_driver.h +++ b/core/rend/vulkan/vulkan_driver.h @@ -26,10 +26,16 @@ class VulkanDriver final : public ImGuiDriver { public: - ~VulkanDriver() { + void reset() + { textures.clear(); linearSampler.reset(); ImGui_ImplVulkan_Shutdown(); + justStarted = true; + } + + ~VulkanDriver() { + reset(); } void newFrame() override { @@ -44,11 +50,7 @@ public: try { bool rendering = context->IsRendering(); if (!rendering) - { - if (context->recreateSwapChainIfNeeded()) - return; - context->NewFrame(); - } + context->NewFrame(); // may reset this driver vk::CommandBuffer vmuCmdBuffer{}; if (!rendering || newFrameStarted) { @@ -69,7 +71,7 @@ public: } void present() override { - getContext()->Present(); // may destroy this driver + getContext()->Present(); // may reset this driver } ImTextureID getTexture(const std::string& name) override { From ff33ff2b88ed46ed2f76936e45d799634fa3590c Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sun, 5 May 2024 16:02:37 +0200 Subject: [PATCH 58/86] ui: change pause menu layout. add icons here and there Always display savestate absolute date. --- core/nullDC.cpp | 7 --- core/rend/gui.cpp | 132 ++++++++++++++++++++++--------------------- core/rend/gui_util.h | 16 ++++++ 3 files changed, 85 insertions(+), 70 deletions(-) diff --git a/core/nullDC.cpp b/core/nullDC.cpp index f3bab50e2..ab474fbe5 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -248,13 +248,6 @@ std::string dc_getStateUpdateDate(int index) struct stat st; if (flycast::stat(filename.c_str(), &st) != 0) return {}; - time_t ago = time(nullptr) - st.st_mtime; - if (ago < 60) - return std::to_string(ago) + " seconds ago"; - if (ago < 3600) - return std::to_string(ago / 60) + " minutes ago"; - if (ago < 3600 * 24) - return std::to_string(ago / 3600) + " hours ago"; tm t; if (localtime_r(&st.st_mtime, &t) == nullptr) return {}; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index befe01e6c..c9a74646e 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -313,9 +313,8 @@ void gui_initFonts() // Font Awesome symbols (added to default font) data = resource::load("fonts/" FONT_ICON_FILE_NAME_FAS, dataSize); verify(data != nullptr); - const float symbolFontSize = 21.f * settings.display.uiScale; static ImWchar faRanges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; - io.Fonts->AddFontFromMemoryTTF(data.release(), dataSize, symbolFontSize, &font_cfg, faRanges); + io.Fonts->AddFontFromMemoryTTF(data.release(), dataSize, fontSize, &font_cfg, faRanges); // Large font without Asian glyphs data = resource::load("fonts/Roboto-Regular.ttf", dataSize); verify(data != nullptr); @@ -590,22 +589,17 @@ static void gui_display_commands() { ImguiStyleVar _{ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)}; // left aligned - float buttonHeight = 50.f; // not scaled - bool lowH = ImGui::GetContentRegionAvail().y < ((100 + 50 * 6) * settings.display.uiScale - + ImGui::GetStyle().FramePadding.y * 2 + ImGui::GetStyle().ItemSpacing.y * 5); - if (lowH) - { - // Low height available (phone): Put game icon in first column without text - // Button columns in next 2 columns - float emptyW = ImGui::GetContentRegionAvail().x - (100 + 150 * 2) * settings.display.uiScale - ImGui::GetStyle().WindowPadding.x * 2; - ImGui::Columns(3, "buttons", false); - ImGui::SetColumnWidth(0, 100 * settings.display.uiScale + ImGui::GetStyle().FramePadding.x * 2 + emptyW / 3); - bool veryLowH = ImGui::GetContentRegionAvail().y < (50 * 6 * settings.display.uiScale - + ImGui::GetStyle().ItemSpacing.y * 5); - if (veryLowH) - buttonHeight = (ImGui::GetContentRegionAvail().y - ImGui::GetStyle().ItemSpacing.y * 5) - / 6 / settings.display.uiScale; - } + float columnWidth = std::min(200.f, + (ImGui::GetContentRegionAvail().x - (100 + 150) * settings.display.uiScale - ImGui::GetStyle().FramePadding.x * 2) + / 2 / settings.display.uiScale); + float buttonWidth = 150.f; // not scaled + bool lowW = ImGui::GetContentRegionAvail().x < ((100 + buttonWidth * 3) * settings.display.uiScale + + ImGui::GetStyle().FramePadding.x * 2 + ImGui::GetStyle().ItemSpacing.x * 2); + if (lowW) + buttonWidth = std::min(150.f, + (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x * 2) + / 3 / settings.display.uiScale); + GameMedia game; game.path = settings.content.path; game.fileName = settings.content.fileName; @@ -614,27 +608,31 @@ static void gui_display_commands() // TODO use placeholder image if not available tex.draw(ScaledVec2(100, 100)); - if (!lowH) - { - ImGui::SameLine(); - ImGui::BeginChild("game_info", ScaledVec2(0, 100.f), ImGuiChildFlags_Border, ImGuiWindowFlags_None); - ImGui::PushFont(largeFont); - ImGui::Text("%s", art.name.c_str()); - ImGui::PopFont(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); - ImGui::TextWrapped("%s", art.fileName.c_str()); - ImGui::PopStyleColor(); - ImGui::EndChild(); + ImGui::SameLine(); + ImGui::BeginChild("game_info", ScaledVec2(0, 100.f), ImGuiChildFlags_Border, ImGuiWindowFlags_None); + ImGui::PushFont(largeFont); + ImGui::Text("%s", art.name.c_str()); + ImGui::PopFont(); + ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); + ImGui::TextWrapped("%s", art.fileName.c_str()); + ImGui::PopStyleColor(); + ImGui::EndChild(); + if (lowW) { ImGui::Columns(3, "buttons", false); - ImGui::SetColumnWidth(0, 100.f * settings.display.uiScale + ImGui::GetStyle().ItemSpacing.x); - ImGui::SetColumnWidth(1, 200.f * settings.display.uiScale); } - ImGui::NextColumn(); + else + { + ImGui::Columns(4, "buttons", false); + ImGui::SetColumnWidth(0, 100.f * settings.display.uiScale + ImGui::GetStyle().ItemSpacing.x); + ImGui::SetColumnWidth(1, columnWidth * settings.display.uiScale); + ImGui::SetColumnWidth(2, columnWidth * settings.display.uiScale); + ImGui::NextColumn(); + } ImguiStyleVar _1{ImGuiStyleVar_FramePadding, ScaledVec2(12.f, 3.f)}; // Resume - if (ImGui::Button(ICON_FA_PLAY " Resume", ScaledVec2(150, buttonHeight))) + if (ImGui::Button(ICON_FA_PLAY " Resume", ScaledVec2(buttonWidth, 50))) { GamepadDevice::load_system_mappings(); gui_setState(GuiState::Closed); @@ -643,19 +641,21 @@ static void gui_display_commands() { DisabledScope _{settings.network.online || settings.raHardcoreMode}; - if (ImGui::Button(ICON_FA_MASK " Cheats", ScaledVec2(150, buttonHeight)) && !settings.network.online) + if (ImGui::Button(ICON_FA_MASK " Cheats", ScaledVec2(buttonWidth, 50)) && !settings.network.online) gui_setState(GuiState::Cheats); } // Achievements { DisabledScope _{!achievements::isActive()}; - if (ImGui::Button(ICON_FA_TROPHY " Achievements", ScaledVec2(150, buttonHeight)) && achievements::isActive()) + if (ImGui::Button(ICON_FA_TROPHY " Achievements", ScaledVec2(buttonWidth, 50)) && achievements::isActive()) gui_setState(GuiState::Achievements); } + ImGui::NextColumn(); + // Insert/Eject Disk const char *disk_label = libGDR_GetDiscType() == Open ? ICON_FA_COMPACT_DISC " Insert Disk" : ICON_FA_COMPACT_DISC " Eject Disk"; - if (ImGui::Button(disk_label, ScaledVec2(150, buttonHeight))) + if (ImGui::Button(disk_label, ScaledVec2(buttonWidth, 50))) { if (libGDR_GetDiscType() == Open) { gui_setState(GuiState::SelectDisk); @@ -666,10 +666,10 @@ static void gui_display_commands() } } // Settings - if (ImGui::Button(ICON_FA_GEAR " Settings", ScaledVec2(150, buttonHeight))) + if (ImGui::Button(ICON_FA_GEAR " Settings", ScaledVec2(buttonWidth, 50))) gui_setState(GuiState::Settings); // Exit - if (ImGui::Button(commandLineStart ? ICON_FA_POWER_OFF " Exit" : ICON_FA_POWER_OFF " Close Game", ScaledVec2(150, buttonHeight))) + if (ImGui::Button(commandLineStart ? ICON_FA_POWER_OFF " Exit" : ICON_FA_POWER_OFF " Close Game", ScaledVec2(buttonWidth, 50))) gui_stop_game(); ImGui::NextColumn(); @@ -679,7 +679,7 @@ static void gui_display_commands() { DisabledScope _{settings.raHardcoreMode}; // Load State - if (ImGui::Button(ICON_FA_CLOCK_ROTATE_LEFT " Load State", ScaledVec2(150, buttonHeight)) && savestateAllowed()) + if (ImGui::Button(ICON_FA_CLOCK_ROTATE_LEFT " Load State", ScaledVec2(buttonWidth, 50)) && savestateAllowed()) { gui_setState(GuiState::Closed); dc_loadstate(config::SavestateSlot); @@ -687,7 +687,7 @@ static void gui_display_commands() } // Save State - if (ImGui::Button(ICON_FA_DOWNLOAD " Save State", ScaledVec2(150, buttonHeight)) && savestateAllowed()) + if (ImGui::Button(ICON_FA_DOWNLOAD " Save State", ScaledVec2(buttonWidth, 50)) && savestateAllowed()) { gui_setState(GuiState::Closed); dc_savestate(config::SavestateSlot); @@ -703,7 +703,7 @@ static void gui_display_commands() SaveSettings(); } std::string slot = "Slot " + std::to_string((int)config::SavestateSlot + 1); - float spacingW = (150.f * settings.display.uiScale - ImGui::GetFrameHeight() * 2 - ImGui::CalcTextSize(slot.c_str()).x) / 2; + float spacingW = (buttonWidth * settings.display.uiScale - ImGui::GetFrameHeight() * 2 - ImGui::CalcTextSize(slot.c_str()).x) / 2; ImGui::SameLine(0, spacingW); ImGui::Text("%s", slot.c_str()); ImGui::SameLine(0, spacingW); @@ -1520,7 +1520,7 @@ static void contentpath_warning_popup() static inline void gui_debug_tab() { - if (ImGui::BeginTabItem("Debug")) + if (ImGui::BeginTabItem(ICON_FA_BUG " Debug")) { ImVec2 normal_padding = ImGui::GetStyle().FramePadding; ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); @@ -1649,11 +1649,15 @@ static void gui_display_settings() ImGui::PopStyleVar(); } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(16, 6)); + if (ImGui::GetContentRegionAvail().x / settings.display.uiScale >= 650.f) + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(16, 6)); + else + // low width + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(4, 6)); if (ImGui::BeginTabBar("settings", ImGuiTabBarFlags_NoTooltip)) { - if (ImGui::BeginTabItem("General")) + if (ImGui::BeginTabItem(ICON_FA_TOOLBOX " General")) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); { @@ -1868,7 +1872,7 @@ static void gui_display_settings() ImGui::PopStyleVar(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Controls")) + if (ImGui::BeginTabItem(ICON_FA_GAMEPAD " Controls")) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); header("Physical Devices"); @@ -2089,7 +2093,7 @@ static void gui_display_settings() ImGui::PopStyleVar(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Video")) + if (ImGui::BeginTabItem(ICON_FA_DISPLAY " Video")) { int renderApi; bool perPixel; @@ -2494,7 +2498,7 @@ static void gui_display_settings() break; } } - if (ImGui::BeginTabItem("Audio")) + if (ImGui::BeginTabItem(ICON_FA_MUSIC " Audio")) // or ICON_FA_VOLUME_OFF? { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); OptionCheckbox("Enable DSP", config::DSPEnabled, @@ -2608,7 +2612,7 @@ static void gui_display_settings() ImGui::PopStyleVar(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Network")) + if (ImGui::BeginTabItem(ICON_FA_WIFI " Network")) { ImGuiStyle& style = ImGui::GetStyle(); ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); @@ -2751,7 +2755,7 @@ static void gui_display_settings() ImGui::PopStyleVar(); ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Advanced")) + if (ImGui::BeginTabItem(ICON_FA_MICROCHIP " Advanced")) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); header("CPU Mode"); @@ -2802,24 +2806,24 @@ static void gui_display_settings() "Automatically upload crash reports to sentry.io to help in troubleshooting. No personal information is included."); #endif } - ImGui::PopStyleVar(); - ImGui::EndTabItem(); - #ifdef USE_LUA +#ifdef USE_LUA header("Lua Scripting"); { ImGui::InputText("Lua Filename", &config::LuaFileName.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); ImGui::SameLine(); ShowHelpMarker("Specify lua filename to use. Should be located in Flycast config directory. Defaults to flycast.lua when empty."); } - #endif +#endif + ImGui::PopStyleVar(); + ImGui::EndTabItem(); } #if !defined(NDEBUG) || defined(DEBUGFAST) || FC_PROFILER gui_debug_tab(); #endif - if (ImGui::BeginTabItem("About")) + if (ImGui::BeginTabItem(ICON_FA_CIRCLE_INFO " About")) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); header("Flycast"); @@ -3021,30 +3025,31 @@ static void gui_display_content() ImGui::Unindent(10 * settings.display.uiScale); static ImGuiTextFilter filter; + const float settingsBtnW = iconButtonWidth(ICON_FA_GEAR, "Settings"); #if !defined(__ANDROID__) && !defined(TARGET_IPHONE) && !defined(TARGET_UWP) && !defined(__SWITCH__) ImGui::SameLine(0, 32 * settings.display.uiScale); filter.Draw("Filter", ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x - 32 * settings.display.uiScale - - ImGui::CalcTextSize("Settings").x - ImGui::GetStyle().FramePadding.x * 2.0f - ImGui::GetStyle().ItemSpacing.x); + - settingsBtnW - ImGui::GetStyle().ItemSpacing.x); #endif if (gui_state != GuiState::SelectDisk) { #ifdef TARGET_UWP void gui_load_game(); - ImGui::SameLine(ImGui::GetContentRegionMax().x - ImGui::CalcTextSize("Settings").x - - ImGui::GetStyle().FramePadding.x * 4.0f - ImGui::GetStyle().ItemSpacing.x - ImGui::CalcTextSize("Load...").x); + ImGui::SameLine(ImGui::GetContentRegionMax().x - settingsBtnW + - ImGui::GetStyle().FramePadding.x * 2.0f - ImGui::GetStyle().ItemSpacing.x - ImGui::CalcTextSize("Load...").x); if (ImGui::Button("Load...")) gui_load_game(); ImGui::SameLine(); #elif defined(__SWITCH__) - ImGui::SameLine(ImGui::GetContentRegionMax().x - ImGui::CalcTextSize("Settings").x - - ImGui::GetStyle().FramePadding.x * 4.0f - ImGui::GetStyle().ItemSpacing.x - ImGui::CalcTextSize("Exit").x); - if (ImGui::Button("Exit")) + ImGui::SameLine(ImGui::GetContentRegionMax().x - settingsBtnW + - ImGui::GetStyle().ItemSpacing.x - iconButtonWidth(ICON_FA_POWER_OFF, "Exit")); + if (iconButton(ICON_FA_POWER_OFF, "Exit")) dc_exit(); ImGui::SameLine(); #else - ImGui::SameLine(ImGui::GetContentRegionMax().x - ImGui::CalcTextSize("Settings").x - ImGui::GetStyle().FramePadding.x * 2.0f); + ImGui::SameLine(ImGui::GetContentRegionMax().x - settingsBtnW); #endif - if (ImGui::Button("Settings")) + if (iconButton(ICON_FA_GEAR, "Settings")) gui_setState(GuiState::Settings); } ImGui::PopStyleVar(); @@ -3462,8 +3467,9 @@ void gui_display_osd() ImGui::Begin("##osd", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground); - ImGui::SetWindowFontScale(1.5); + ImGui::PushFont(largeFont); ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", message.c_str()); + ImGui::PopFont(); ImGui::End(); } imguiDriver->displayCrosshairs(); diff --git a/core/rend/gui_util.h b/core/rend/gui_util.h index 4c5f15420..1633410fb 100644 --- a/core/rend/gui_util.h +++ b/core/rend/gui_util.h @@ -215,3 +215,19 @@ public: private: std::string path; }; + +static inline bool iconButton(const char *icon, const std::string& label, const ImVec2& size = {}) +{ + ImguiStyleVar _{ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)}; // left aligned + std::string s(5 + label.size(), '\0'); + s.resize(sprintf(s.data(), "%s %s", icon, label.c_str())); + return ImGui::Button(s.c_str(), size); +} + +static inline float iconButtonWidth(const char *icon, const std::string& label) +{ + // TODO avoid doing stuff twice + std::string s(5 + label.size(), '\0'); + s.resize(sprintf(s.data(), "%s %s", icon, label.c_str())); + return ImGui::CalcTextSize(s.c_str()).x + ImGui::GetStyle().FramePadding.x * 2; +} From f04e9d4a651fb7559e1833e6386c25c61ecdc7d4 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 6 May 2024 14:35:00 +0200 Subject: [PATCH 59/86] achievements: better game load message when no achievements Issue #761 --- core/achievements/achievements.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp index ecbd6b0d6..431839356 100644 --- a/core/achievements/achievements.cpp +++ b/core/achievements/achievements.cpp @@ -824,7 +824,7 @@ void Achievements::gameLoaded(int result, const char *errorMessage) EventManager::listen(Event::VBlank, emuEventCallback, this); NOTICE_LOG(COMMON, "RA: game %d loaded: %s, achievements %d leaderboards %d rich presence %d", info->id, info->title, rc_client_has_achievements(rc_client), rc_client_has_leaderboards(rc_client), rc_client_has_rich_presence(rc_client)); - if (!rc_client_has_achievements(rc_client)) + if (!rc_client_is_processing_required(rc_client)) settings.raHardcoreMode = false; else settings.raHardcoreMode = (bool)rc_client_get_hardcore_enabled(rc_client); @@ -834,8 +834,12 @@ void Achievements::gameLoaded(int result, const char *errorMessage) image = getOrDownloadImage(url); rc_client_user_game_summary_t summary; rc_client_get_user_game_summary(rc_client, &summary); - std::string text = "You have " + std::to_string(summary.num_unlocked_achievements) - + " of " + std::to_string(summary.num_core_achievements) + " achievements unlocked"; + std::string text; + if (summary.num_core_achievements > 0) + text = "You have " + std::to_string(summary.num_unlocked_achievements) + + " of " + std::to_string(summary.num_core_achievements) + " achievements unlocked."; + else + text = "This game has no achievements."; std::string text2 = settings.raHardcoreMode ? "Hardcore Mode" : ""; notifier.notify(Notification::Login, image, info->title, text, text2); } From 7327e52e713d8bdac143a6fdb4465f02fedec3c5 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 6 May 2024 14:45:28 +0200 Subject: [PATCH 60/86] ui: crash when loading asian fonts on windows FontNo must be reset to 0 for Font Awesome. Issue #1503 Fixes MINIDUMP-314 --- core/rend/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index c9a74646e..2ec3a4fd5 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -313,6 +313,7 @@ void gui_initFonts() // Font Awesome symbols (added to default font) data = resource::load("fonts/" FONT_ICON_FILE_NAME_FAS, dataSize); verify(data != nullptr); + font_cfg.FontNo = 0; static ImWchar faRanges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 }; io.Fonts->AddFontFromMemoryTTF(data.release(), dataSize, fontSize, &font_cfg, faRanges); // Large font without Asian glyphs From ceec01ac2e42918169a57757dbf8323bfcda4fea Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 6 May 2024 21:31:04 +0200 Subject: [PATCH 61/86] ui: use imgui for vmu on pause menu. toast notifications Allow nearest sampling with imgui textures (vmu, crosshair) Use raii for styles and colors --- core/deps/imgui/backends/imgui_impl_dx11.cpp | 30 ++- core/deps/imgui/backends/imgui_impl_dx11.h | 8 + core/deps/imgui/backends/imgui_impl_dx9.cpp | 39 ++- core/deps/imgui/backends/imgui_impl_dx9.h | 8 + core/network/ggpo.cpp | 9 +- core/nullDC.cpp | 14 +- core/rend/dx11/dx11_driver.h | 9 +- core/rend/dx11/dx11_overlay.cpp | 4 +- core/rend/dx11/dx11_overlay.h | 2 + core/rend/dx11/dx11context.cpp | 4 - core/rend/dx9/d3d_overlay.cpp | 4 +- core/rend/dx9/d3d_overlay.h | 6 +- core/rend/dx9/dx9_driver.h | 30 ++- core/rend/dx9/dxcontext.cpp | 4 - core/rend/gles/gldraw.cpp | 5 +- core/rend/gles/opengl_driver.cpp | 57 ++-- core/rend/gles/opengl_driver.h | 3 +- core/rend/gui.cpp | 265 +++++++++---------- core/rend/gui_achievements.cpp | 16 +- core/rend/gui_chat.h | 5 +- core/rend/gui_cheats.cpp | 91 +++---- core/rend/gui_util.cpp | 180 ++++++++----- core/rend/gui_util.h | 28 +- core/rend/imgui_driver.cpp | 58 +++- core/rend/imgui_driver.h | 20 +- core/rend/osd.cpp | 4 +- core/rend/osd.h | 4 +- core/rend/vulkan/overlay.cpp | 5 +- core/rend/vulkan/overlay.h | 1 + core/rend/vulkan/vulkan_driver.h | 55 ++-- shell/libretro/libretro.cpp | 2 +- 31 files changed, 583 insertions(+), 387 deletions(-) diff --git a/core/deps/imgui/backends/imgui_impl_dx11.cpp b/core/deps/imgui/backends/imgui_impl_dx11.cpp index b878e79f8..6bd023881 100644 --- a/core/deps/imgui/backends/imgui_impl_dx11.cpp +++ b/core/deps/imgui/backends/imgui_impl_dx11.cpp @@ -59,7 +59,8 @@ struct ImGui_ImplDX11_Data ID3D11PixelShader* pPixelShader; ID3D11SamplerState* pFontSampler; ID3D11SamplerState* pTextureSampler; - ID3D11ShaderResourceView* pFontTextureView; + ID3D11SamplerState* pPointSampler; + ImTextureDX11 FontTexture; ID3D11RasterizerState* pRasterizerState; ID3D11BlendState* pBlendState; ID3D11DepthStencilState* pDepthStencilState; @@ -279,11 +280,17 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data) ctx->RSSetScissorRects(1, &r); // Bind texture, Draw - ID3D11ShaderResourceView* texture_srv = (ID3D11ShaderResourceView*)pcmd->GetTexID(); - if (pcmd->TextureId != (ImTextureID)bd->pFontTextureView) - ctx->PSSetSamplers(0, 1, &bd->pTextureSampler); + ImTextureDX11 *tex = (ImTextureDX11 *)pcmd->GetTexID(); + if (tex != &bd->FontTexture) + { + if (tex->pointSampling) + ctx->PSSetSamplers(0, 1, &bd->pPointSampler); + else + ctx->PSSetSamplers(0, 1, &bd->pTextureSampler); + } else ctx->PSSetSamplers(0, 1, &bd->pFontSampler); + ID3D11ShaderResourceView* texture_srv = tex->shaderResourceView; ctx->PSSetShaderResources(0, 1, &texture_srv); ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset); } @@ -350,12 +357,12 @@ static void ImGui_ImplDX11_CreateFontsTexture() srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MostDetailedMip = 0; - bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView); + bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->FontTexture.shaderResourceView); pTexture->Release(); } // Store our identifier - io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView); + io.Fonts->SetTexID((ImTextureID)&bd->FontTexture); // Create texture samplers // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) @@ -375,6 +382,9 @@ static void ImGui_ImplDX11_CreateFontsTexture() desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER; desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER; bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTextureSampler); + + desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + bd->pd3dDevice->CreateSamplerState(&desc, &bd->pPointSampler); } } @@ -540,7 +550,13 @@ void ImGui_ImplDX11_InvalidateDeviceObjects() if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; } if (bd->pTextureSampler) { bd->pTextureSampler->Release(); bd->pTextureSampler = nullptr; } - if (bd->pFontTextureView) { bd->pFontTextureView->Release(); bd->pFontTextureView = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well. + if (bd->pPointSampler) { bd->pPointSampler->Release(); bd->pPointSampler = nullptr; } + if (bd->FontTexture.shaderResourceView) { + bd->FontTexture.shaderResourceView->Release(); + bd->FontTexture.shaderResourceView = nullptr; + // We copied data->FontTexture.shaderResourceView to io.Fonts->TexID so let's clear that as well. + ImGui::GetIO().Fonts->SetTexID(0); + } if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } if (bd->pBlendState) { bd->pBlendState->Release(); bd->pBlendState = nullptr; } diff --git a/core/deps/imgui/backends/imgui_impl_dx11.h b/core/deps/imgui/backends/imgui_impl_dx11.h index 20887f370..be5657bd6 100644 --- a/core/deps/imgui/backends/imgui_impl_dx11.h +++ b/core/deps/imgui/backends/imgui_impl_dx11.h @@ -19,6 +19,7 @@ struct ID3D11Device; struct ID3D11DeviceContext; +struct ID3D11ShaderResourceView; IMGUI_IMPL_API bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_context); IMGUI_IMPL_API void ImGui_ImplDX11_Shutdown(); @@ -29,4 +30,11 @@ IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data); IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects(); +// ImTextureID should be a pointer to this struct +struct ImTextureDX11 +{ + ID3D11ShaderResourceView *shaderResourceView; + bool pointSampling; +}; + #endif // #ifndef IMGUI_DISABLE diff --git a/core/deps/imgui/backends/imgui_impl_dx9.cpp b/core/deps/imgui/backends/imgui_impl_dx9.cpp index fcda7b4f7..9e14bf7d0 100644 --- a/core/deps/imgui/backends/imgui_impl_dx9.cpp +++ b/core/deps/imgui/backends/imgui_impl_dx9.cpp @@ -48,7 +48,7 @@ struct ImGui_ImplDX9_Data LPDIRECT3DDEVICE9 pd3dDevice; LPDIRECT3DVERTEXBUFFER9 pVB; LPDIRECT3DINDEXBUFFER9 pIB; - LPDIRECT3DTEXTURE9 FontTexture; + ImTextureDX9 FontTexture; int VertexBufferSize; int IndexBufferSize; @@ -244,17 +244,30 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) // Apply Scissor/clipping rectangle, Bind texture, Draw const RECT r = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y }; - const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->GetTexID(); + const ImTextureDX9 *tex = (const ImTextureDX9 *)pcmd->GetTexID(); + const LPDIRECT3DTEXTURE9 texture = tex->d3dTexture; bd->pd3dDevice->SetTexture(0, texture); - if (texture != bd->FontTexture) + if (tex != &bd->FontTexture) { bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_BORDER); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_BORDER); + if (tex->pointSampling) + { + bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_POINT); + bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_POINT); + } + else + { + bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + } } else { bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); bd->pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); + bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + bd->pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); } bd->pd3dDevice->SetScissorRect(&r); bd->pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); @@ -338,18 +351,18 @@ static bool ImGui_ImplDX9_CreateFontsTexture() #endif // Upload texture to graphics system - bd->FontTexture = nullptr; - if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, rgba_support ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0) + bd->FontTexture.d3dTexture = nullptr; + if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, rgba_support ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture.d3dTexture, nullptr) < 0) return false; D3DLOCKED_RECT tex_locked_rect; - if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK) + if (bd->FontTexture.d3dTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK) return false; for (int y = 0; y < height; y++) memcpy((unsigned char*)tex_locked_rect.pBits + (size_t)tex_locked_rect.Pitch * y, pixels + (size_t)width * bytes_per_pixel * y, (size_t)width * bytes_per_pixel); - bd->FontTexture->UnlockRect(0); + bd->FontTexture.d3dTexture->UnlockRect(0); // Store our identifier - io.Fonts->SetTexID((ImTextureID)bd->FontTexture); + io.Fonts->SetTexID((ImTextureID)&bd->FontTexture); #ifndef IMGUI_USE_BGRA_PACKED_COLOR if (!rgba_support && io.Fonts->TexPixelsUseColors) @@ -376,7 +389,13 @@ void ImGui_ImplDX9_InvalidateDeviceObjects() return; if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } - if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well. + if (bd->FontTexture.d3dTexture) + { + bd->FontTexture.d3dTexture->Release(); + bd->FontTexture.d3dTexture = nullptr; + // We copied bd->FontTexture to io.Fonts->TexID so let's clear that as well. + ImGui::GetIO().Fonts->SetTexID(0); + } } void ImGui_ImplDX9_NewFrame() @@ -384,7 +403,7 @@ void ImGui_ImplDX9_NewFrame() ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX9_Init()?"); - if (!bd->FontTexture) + if (!bd->FontTexture.d3dTexture) ImGui_ImplDX9_CreateDeviceObjects(); } diff --git a/core/deps/imgui/backends/imgui_impl_dx9.h b/core/deps/imgui/backends/imgui_impl_dx9.h index 3965583bd..e8205a6e3 100644 --- a/core/deps/imgui/backends/imgui_impl_dx9.h +++ b/core/deps/imgui/backends/imgui_impl_dx9.h @@ -18,6 +18,7 @@ #ifndef IMGUI_DISABLE struct IDirect3DDevice9; +struct IDirect3DTexture9; IMGUI_IMPL_API bool ImGui_ImplDX9_Init(IDirect3DDevice9* device); IMGUI_IMPL_API void ImGui_ImplDX9_Shutdown(); @@ -28,4 +29,11 @@ IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data); IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects(); +// ImTextureID should be a pointer to this struct +struct ImTextureDX9 +{ + IDirect3DTexture9 *d3dTexture; + bool pointSampling; +}; + #endif // #ifndef IMGUI_DISABLE diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp index dbcb325a2..4a2a506ff 100644 --- a/core/network/ggpo.cpp +++ b/core/network/ggpo.cpp @@ -73,6 +73,7 @@ static void getLocalInput(MapleInputState inputState[4]) #include "ggponet.h" #include "emulator.h" #include "rend/gui.h" +#include "rend/gui_util.h" #include "hw/mem/mem_watch.h" #include #include @@ -857,13 +858,13 @@ void displayStats() GGPONetworkStats stats; ggpo_get_network_stats(ggpoSession, remotePlayer, &stats); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); + ImguiStyleVar _1(ImGuiStyleVar_WindowBorderSize, 0); ImGui::SetNextWindowPos(ImVec2(10, 10)); ImGui::SetNextWindowSize(ImVec2(95 * settings.display.uiScale, 0)); ImGui::SetNextWindowBgAlpha(0.7f); ImGui::Begin("##ggpostats", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs); - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.557f, 0.268f, 0.965f, 1.f)); + ImguiStyleColor _2(ImGuiCol_PlotHistogram, ImVec4(0.557f, 0.268f, 0.965f, 1.f)); // Send Queue ImGui::Text("Send Q"); @@ -905,9 +906,7 @@ void displayStats() timesyncOccurred--; } - ImGui::PopStyleColor(); ImGui::End(); - ImGui::PopStyleVar(2); } void endOfFrame() diff --git a/core/nullDC.cpp b/core/nullDC.cpp index ab474fbe5..0d69d71a1 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -105,7 +105,7 @@ void dc_savestate(int index) if (data == nullptr) { WARN_LOG(SAVESTATE, "Failed to save state - could not malloc %d bytes", (int)ser.size()); - gui_display_notification("Save state failed - memory full", 2000); + gui_display_notification("Save state failed - memory full", 5000); return; } @@ -119,7 +119,7 @@ void dc_savestate(int index) if ( f == NULL ) { WARN_LOG(SAVESTATE, "Failed to save state - could not open %s for writing", filename.c_str()); - gui_display_notification("Cannot open save file", 2000); + gui_display_notification("Cannot open save file", 5000); free(data); return; } @@ -131,14 +131,14 @@ void dc_savestate(int index) if (!zipFile.Open(filename, true)) { WARN_LOG(SAVESTATE, "Failed to save state - could not open %s for writing", filename.c_str()); - gui_display_notification("Cannot open save file", 2000); + gui_display_notification("Cannot open save file", 5000); free(data); return; } if (zipFile.Write(data, ser.size()) != ser.size()) { WARN_LOG(SAVESTATE, "Failed to save state - error writing %s", filename.c_str()); - gui_display_notification("Error saving state", 2000); + gui_display_notification("Error saving state", 5000); zipFile.Close(); free(data); return; @@ -148,7 +148,7 @@ void dc_savestate(int index) free(data); NOTICE_LOG(SAVESTATE, "Saved state to %s size %d", filename.c_str(), (int)ser.size()); - gui_display_notification("State saved", 1000); + gui_display_notification("State saved", 2000); } void dc_loadstate(int index) @@ -194,7 +194,7 @@ void dc_loadstate(int index) if (data == nullptr) { WARN_LOG(SAVESTATE, "Failed to load state - could not malloc %d bytes", total_size); - gui_display_notification("Failed to load state - memory full", 2000); + gui_display_notification("Failed to load state - memory full", 5000); if (f != nullptr) std::fclose(f); else @@ -216,7 +216,7 @@ void dc_loadstate(int index) if (read_size != total_size) { WARN_LOG(SAVESTATE, "Failed to load state - I/O error"); - gui_display_notification("Failed to load state - I/O error", 2000); + gui_display_notification("Failed to load state - I/O error", 5000); free(data); return; } diff --git a/core/rend/dx11/dx11_driver.h b/core/rend/dx11/dx11_driver.h index 20bf86999..a10c59f09 100644 --- a/core/rend/dx11/dx11_driver.h +++ b/core/rend/dx11/dx11_driver.h @@ -57,12 +57,12 @@ public: ImTextureID getTexture(const std::string& name) override { auto it = textures.find(name); if (it != textures.end()) - return (ImTextureID)it->second.textureView.get(); + return (ImTextureID)&it->second.imTexture; else return ImTextureID{}; } - ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) override + ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) override { Texture& texture = textures[name]; texture.texture.reset(); @@ -86,8 +86,10 @@ public: theDX11Context.getDevice()->CreateShaderResourceView(texture.texture, &viewDesc, &texture.textureView.get()); theDX11Context.getDeviceContext()->UpdateSubresource(texture.texture, 0, nullptr, data, width * 4, width * 4 * height); + texture.imTexture.shaderResourceView = texture.textureView.get(); + texture.imTexture.pointSampling = nearestSampling; - return (ImTextureID)texture.textureView.get(); + return (ImTextureID)&texture.imTexture; } private: @@ -95,6 +97,7 @@ private: { ComPtr texture; ComPtr textureView; + ImTextureDX11 imTexture; }; bool frameRendered = false; diff --git a/core/rend/dx11/dx11_overlay.cpp b/core/rend/dx11/dx11_overlay.cpp index dd62d5105..ff507c56e 100644 --- a/core/rend/dx11/dx11_overlay.cpp +++ b/core/rend/dx11/dx11_overlay.cpp @@ -56,7 +56,7 @@ void DX11Overlay::draw(u32 width, u32 height, bool vmu, bool crosshair) vmuTextures[i].reset(); continue; } - if (vmuTextures[i] == nullptr || vmu_lcd_changed[i]) + if (vmuTextures[i] == nullptr || this->vmuLastChanged[i] != ::vmuLastChanged[i]) { vmuTextureViews[i].reset(); vmuTextures[i].reset(); @@ -82,8 +82,8 @@ void DX11Overlay::draw(u32 width, u32 height, bool vmu, bool crosshair) for (int y = 0; y < 32; y++) memcpy(&data[y * 48], &vmu_lcd_data[i][(31 - y) * 48], sizeof(u32) * 48); deviceContext->UpdateSubresource(vmuTextures[i], 0, nullptr, data, 48 * 4, 48 * 4 * 32); + this->vmuLastChanged[i] = ::vmuLastChanged[i]; } - vmu_lcd_changed[i] = false; } float x, y; float w = vmu_width; diff --git a/core/rend/dx11/dx11_overlay.h b/core/rend/dx11/dx11_overlay.h index fbdf54c20..73ff81bcc 100644 --- a/core/rend/dx11/dx11_overlay.h +++ b/core/rend/dx11/dx11_overlay.h @@ -35,6 +35,7 @@ public: this->deviceContext = deviceContext; this->samplers = samplers; quad.init(device, deviceContext, shaders); + vmuLastChanged.fill({}); } void term() @@ -60,6 +61,7 @@ private: ComPtr xhairTextureView; std::array, 8> vmuTextures; std::array, 8> vmuTextureViews; + std::array vmuLastChanged {}; Quad quad; Samplers *samplers; BlendStates blendStates; diff --git a/core/rend/dx11/dx11context.cpp b/core/rend/dx11/dx11context.cpp index 26f71739e..abc8a3e9b 100644 --- a/core/rend/dx11/dx11context.cpp +++ b/core/rend/dx11/dx11context.cpp @@ -260,10 +260,6 @@ void DX11Context::EndImGuiFrame() if (crosshairsNeeded() || config::FloatVMUs) overlay.draw(settings.display.width, settings.display.height, config::FloatVMUs, true); } - else - { - overlay.draw(settings.display.width, settings.display.height, true, false); - } ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData()); } frameRendered = true; diff --git a/core/rend/dx9/d3d_overlay.cpp b/core/rend/dx9/d3d_overlay.cpp index c26f1a975..b62c2ecb7 100644 --- a/core/rend/dx9/d3d_overlay.cpp +++ b/core/rend/dx9/d3d_overlay.cpp @@ -50,7 +50,7 @@ void D3DOverlay::draw(u32 width, u32 height, bool vmu, bool crosshair) texture.reset(); continue; } - if (texture == nullptr || vmu_lcd_changed[i]) + if (texture == nullptr || this->vmuLastChanged[i] != ::vmuLastChanged[i]) { texture.reset(); device->CreateTexture(48, 32, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &texture.get(), 0); @@ -61,8 +61,8 @@ void D3DOverlay::draw(u32 width, u32 height, bool vmu, bool crosshair) for (int y = 0; y < 32; y++) memcpy(dst + y * rect.Pitch, vmu_lcd_data[i] + (31 - y) * 48, 48 * 4); texture->UnlockRect(0); + this->vmuLastChanged[i] = ::vmuLastChanged[i]; } - vmu_lcd_changed[i] = false; } float x; if (i & 2) diff --git a/core/rend/dx9/d3d_overlay.h b/core/rend/dx9/d3d_overlay.h index 8288cdf76..3852de40e 100644 --- a/core/rend/dx9/d3d_overlay.h +++ b/core/rend/dx9/d3d_overlay.h @@ -28,6 +28,7 @@ class D3DOverlay public: void init(const ComPtr& device) { this->device = device; + vmuLastChanged.fill({}); } void term() { @@ -45,11 +46,12 @@ private: struct Vertex { - float pos[3]; - float uv[2]; + float pos[3]; + float uv[2]; }; ComPtr device; ComPtr xhairTexture; std::array, 8> vmuTextures; + std::array vmuLastChanged {}; }; diff --git a/core/rend/dx9/dx9_driver.h b/core/rend/dx9/dx9_driver.h index 20e615c5a..a6ceb6d6b 100644 --- a/core/rend/dx9/dx9_driver.h +++ b/core/rend/dx9/dx9_driver.h @@ -53,20 +53,21 @@ public: frameRendered = true; } - ImTextureID getTexture(const std::string& name) override { + ImTextureID getTexture(const std::string& name) override + { auto it = textures.find(name); if (it != textures.end()) - return (ImTextureID)it->second.get(); + return (ImTextureID)&it->second.imTexture; else return ImTextureID{}; } - ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) override + ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) override { - ComPtr& texture = textures[name]; - texture.reset(); - HRESULT hr = theDXContext.getDevice()->CreateTexture(width, height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &texture.get(), 0); - if (FAILED(hr) || !texture) + Texture& texture = textures[name]; + texture.tex.reset(); + HRESULT hr = theDXContext.getDevice()->CreateTexture(width, height, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &texture.tex.get(), 0); + if (FAILED(hr) || !texture.tex) { WARN_LOG(RENDERER, "CreateTexture failed (%d x %d): error %x", width, height, hr); textures.erase(name); @@ -76,7 +77,7 @@ public: width *= 4; D3DLOCKED_RECT rect; - texture->LockRect(0, &rect, nullptr, 0); + texture.tex->LockRect(0, &rect, nullptr, 0); u8 *dst = (u8 *)rect.pBits; const u8 *src = data; for (int y = 0; y < height; y++) @@ -92,12 +93,19 @@ public: dst += rect.Pitch; src += width; } - texture->UnlockRect(0); + texture.tex->UnlockRect(0); + texture.imTexture.d3dTexture = texture.tex.get(); + texture.imTexture.pointSampling = nearestSampling; - return (ImTextureID)texture.get(); + return (ImTextureID)&texture; } private: bool frameRendered = false; - std::unordered_map> textures; + struct Texture + { + ComPtr tex; + ImTextureDX9 imTexture; + }; + std::unordered_map textures; }; diff --git a/core/rend/dx9/dxcontext.cpp b/core/rend/dx9/dxcontext.cpp index 7dc79a9b6..f57306725 100644 --- a/core/rend/dx9/dxcontext.cpp +++ b/core/rend/dx9/dxcontext.cpp @@ -200,10 +200,6 @@ void DXContext::EndImGuiFrame() if (crosshairsNeeded() || config::FloatVMUs) overlay.draw(settings.display.width, settings.display.height, config::FloatVMUs, true); } - else - { - overlay.draw(settings.display.width, settings.display.height, true, false); - } ImGui_ImplDX9_RenderDrawData(ImGui::GetDrawData()); pDevice->EndScene(); } diff --git a/core/rend/gles/gldraw.cpp b/core/rend/gles/gldraw.cpp index d4a06ab9e..a2384fe03 100644 --- a/core/rend/gles/gldraw.cpp +++ b/core/rend/gles/gldraw.cpp @@ -822,6 +822,7 @@ bool OpenGLRenderer::renderLastFrame() static GLuint vmuTextureId[4] {}; static GLuint lightgunTextureId[4] {}; +static u64 vmuLastUpdated[4] {}; static void updateVmuTexture(int vmu_screen_number) { @@ -839,7 +840,7 @@ static void updateVmuTexture(int vmu_screen_number) const u32 *data = vmu_lcd_data[vmu_screen_number * 2]; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VMU_SCREEN_WIDTH, VMU_SCREEN_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - vmu_lcd_changed[vmu_screen_number * 2] = false; + vmuLastUpdated[vmu_screen_number * 2] = vmuLastChanged[vmu_screen_number * 2]; } void DrawVmuTexture(u8 vmu_screen_number, int width, int height) @@ -849,7 +850,7 @@ void DrawVmuTexture(u8 vmu_screen_number, int width, int height) float w = (float)VMU_SCREEN_WIDTH * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult * 4.f / 3.f / gl.ofbo.aspectRatio * width / 640.f; float h = (float)VMU_SCREEN_HEIGHT * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult * height / 480.f; - if (vmu_lcd_changed[vmu_screen_number * 2] || vmuTextureId[vmu_screen_number] == 0) + if (vmuLastChanged[vmu_screen_number * 2] != vmuLastUpdated[vmu_screen_number * 2] || vmuTextureId[vmu_screen_number] == 0) updateVmuTexture(vmu_screen_number); switch (vmu_screen_params[vmu_screen_number].vmu_screen_position) diff --git a/core/rend/gles/opengl_driver.cpp b/core/rend/gles/opengl_driver.cpp index 48b259277..60663cfb9 100644 --- a/core/rend/gles/opengl_driver.cpp +++ b/core/rend/gles/opengl_driver.cpp @@ -21,6 +21,7 @@ #include "wsi/gl_context.h" #include "rend/osd.h" #include "rend/gui.h" +#include "rend/gui_util.h" #include "glcache.h" #include "gles.h" @@ -41,14 +42,12 @@ static constexpr int vmu_coords[8][2] = { { 1 , 1 }, { 1 , 1 }, }; -constexpr int VMU_WIDTH = 70 * 48 / 32; -constexpr int VMU_HEIGHT = 70; +constexpr int VMU_WIDTH = 96; +constexpr int VMU_HEIGHT = 64; constexpr int VMU_PADDING = 8; OpenGLDriver::OpenGLDriver() { - for (auto& tex : vmu_lcd_tex_ids) - tex = ImTextureID(); ImGui_ImplOpenGL3_Init(); EventManager::listen(Event::Resume, emuEventCallback, this); EventManager::listen(Event::Terminate, emuEventCallback, this); @@ -60,10 +59,7 @@ OpenGLDriver::~OpenGLDriver() EventManager::unlisten(Event::Terminate, emuEventCallback, this); std::vector texIds; - texIds.reserve(std::size(vmu_lcd_tex_ids) + 1 + textures.size()); - for (ImTextureID texId : vmu_lcd_tex_ids) - if (texId != ImTextureID()) - texIds.push_back((GLuint)(uintptr_t)texId); + texIds.reserve(1 + textures.size()); if (crosshairTexId != ImTextureID()) texIds.push_back((GLuint)(uintptr_t)crosshairTexId); for (const auto& it : textures) @@ -77,46 +73,37 @@ void OpenGLDriver::displayVmus() { if (!gameStarted) return; - ImGui::SetNextWindowBgAlpha(0); - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); - - ImGui::Begin("vmu-window", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs - | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoFocusOnAppearing); - const float width = VMU_WIDTH * settings.display.uiScale; - const float height = VMU_HEIGHT * settings.display.uiScale; - const float padding = VMU_PADDING * settings.display.uiScale; + updateVmuTextures(); + const ScaledVec2 size(VMU_WIDTH, VMU_HEIGHT); + const float padding = uiScaled(VMU_PADDING); + ImDrawList *dl = ImGui::GetForegroundDrawList(); for (int i = 0; i < 8; i++) { if (!vmu_lcd_status[i]) continue; - if (vmu_lcd_changed[i] || vmu_lcd_tex_ids[i] == ImTextureID()) - vmu_lcd_tex_ids[i] = updateTexture("__vmu" + std::to_string(i), (const u8 *)vmu_lcd_data[i], 48, 32); - int x = vmu_coords[i][0]; int y = vmu_coords[i][1]; ImVec2 pos; if (x == 0) pos.x = padding; else - pos.x = ImGui::GetIO().DisplaySize.x - width - padding; + pos.x = ImGui::GetIO().DisplaySize.x - size.x - padding; if (y == 0) { pos.y = padding; if (i & 1) - pos.y += height + padding; + pos.y += size.y + padding; } else { - pos.y = ImGui::GetIO().DisplaySize.y - height - padding; + pos.y = ImGui::GetIO().DisplaySize.y - size.y - padding; if (i & 1) - pos.y -= height + padding; + pos.y -= size.y + padding; } - ImVec2 pos_b(pos.x + width, pos.y + height); - ImGui::GetWindowDrawList()->AddImage(vmu_lcd_tex_ids[i], pos, pos_b, ImVec2(0, 1), ImVec2(1, 0), 0xC0ffffff); + ImVec2 pos_b = pos + size; + dl->AddImage(vmu_lcd_tex_ids[i], pos, pos_b, ImVec2(0, 1), ImVec2(1, 0), 0xC0ffffff); } - ImGui::End(); } void OpenGLDriver::displayCrosshairs() @@ -127,7 +114,7 @@ void OpenGLDriver::displayCrosshairs() return; if (crosshairTexId == ImTextureID()) - crosshairTexId = updateTexture("__crosshair", (const u8 *)getCrosshairTextureData(), 16, 16); + crosshairTexId = updateTexture("__crosshair", (const u8 *)getCrosshairTextureData(), 16, 16, true); ImGui::SetNextWindowBgAlpha(0); ImGui::SetNextWindowPos(ImVec2(0, 0)); @@ -172,15 +159,21 @@ void OpenGLDriver::present() frameRendered = false; } -ImTextureID OpenGLDriver::updateTexture(const std::string& name, const u8 *data, int width, int height) +ImTextureID OpenGLDriver::updateTexture(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) { ImTextureID oldId = getTexture(name); if (oldId != ImTextureID()) glcache.DeleteTextures(1, (GLuint *)&oldId); GLuint texId = glcache.GenTexture(); - glcache.BindTexture(GL_TEXTURE_2D, texId); - glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glcache.BindTexture(GL_TEXTURE_2D, texId); + if (nearestSampling) { + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + } + else { + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } if (gl.border_clamp_supported) { float color[] = { 0.0f, 0.0f, 0.0f, 0.0f }; diff --git a/core/rend/gles/opengl_driver.h b/core/rend/gles/opengl_driver.h index 9588a9965..c100e6c95 100644 --- a/core/rend/gles/opengl_driver.h +++ b/core/rend/gles/opengl_driver.h @@ -46,7 +46,7 @@ public: return ImTextureID{}; } - ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) override; + ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) override; private: void emuEvent(Event event) @@ -67,7 +67,6 @@ private: ((OpenGLDriver *)p)->emuEvent(event); } - ImTextureID vmu_lcd_tex_ids[8]; ImTextureID crosshairTexId = ImTextureID(); bool gameStarted = false; bool frameRendered = false; diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 2ec3a4fd5..a5edb5e24 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -99,6 +99,7 @@ static std::recursive_mutex guiMutex; using LockGuard = std::lock_guard; ImFont *largeFont; +static Toast toast; static void emuEventCallback(Event event, void *) { @@ -581,7 +582,6 @@ static bool savestateAllowed() static void gui_display_commands() { - imguiDriver->displayVmus(); fullScreenWindow(false); ImGui::SetNextWindowBgAlpha(0.8f); ImguiStyleVar _{ImGuiStyleVar_WindowBorderSize, 0}; @@ -614,9 +614,10 @@ static void gui_display_commands() ImGui::PushFont(largeFont); ImGui::Text("%s", art.name.c_str()); ImGui::PopFont(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); - ImGui::TextWrapped("%s", art.fileName.c_str()); - ImGui::PopStyleColor(); + { + ImguiStyleColor _(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); + ImGui::TextWrapped("%s", art.fileName.c_str()); + } ImGui::EndChild(); if (lowW) { @@ -628,6 +629,9 @@ static void gui_display_commands() ImGui::SetColumnWidth(0, 100.f * settings.display.uiScale + ImGui::GetStyle().ItemSpacing.x); ImGui::SetColumnWidth(1, columnWidth * settings.display.uiScale); ImGui::SetColumnWidth(2, columnWidth * settings.display.uiScale); + const ImVec2 vmuPos = ImGui::GetStyle().WindowPadding + ScaledVec2(0.f, 100.f) + + ImVec2(insetLeft, ImGui::GetStyle().ItemSpacing.y); + imguiDriver->displayVmus(vmuPos); ImGui::NextColumn(); } ImguiStyleVar _1{ImGuiStyleVar_FramePadding, ScaledVec2(12.f, 3.f)}; @@ -743,13 +747,11 @@ static void gui_display_commands() inline static void header(const char *title) { - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left - ImGui::PushStyleVar(ImGuiStyleVar_DisabledAlpha, 1.0f); + ImguiStyleVar _(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left + ImguiStyleVar _1(ImGuiStyleVar_DisabledAlpha, 1.0f); ImGui::BeginDisabled(); ImGui::ButtonEx(title, ImVec2(-1, 0)); ImGui::EndDisabled(); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); } const char *maple_device_types[] = @@ -1059,8 +1061,8 @@ static DreamcastKey getOppositeDirectionKey(DreamcastKey key) static void detect_input_popup(const Mapping *mapping) { ImVec2 padding = ScaledVec2(20, 20); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, padding); + ImguiStyleVar _(ImGuiStyleVar_WindowPadding, padding); + ImguiStyleVar _1(ImGuiStyleVar_ItemSpacing, padding); if (ImGui::BeginPopupModal("Map Control", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { ImGui::Text("Waiting for control '%s'...", mapping->name); @@ -1097,7 +1099,6 @@ static void detect_input_popup(const Mapping *mapping) } ImGui::EndPopup(); } - ImGui::PopStyleVar(2); } static void displayLabelOrCode(const char *label, u32 code, const char *suffix = "") @@ -1129,7 +1130,7 @@ static void displayMappedControl(const std::shared_ptr& gamepad, static void controller_mapping_popup(const std::shared_ptr& gamepad) { fullScreenWindow(true); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); + ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); if (ImGui::BeginPopupModal("Controller Mapping", NULL, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { const ImGuiStyle& style = ImGui::GetStyle(); @@ -1159,7 +1160,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep if (gamepad->maple_port() == MAPLE_PORTS) { ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, (30 * scaling - ImGui::GetFontSize()) / 2)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, (30 * scaling - ImGui::GetFontSize()) / 2)); portWidth = ImGui::CalcTextSize("AA").x + ImGui::GetStyle().ItemSpacing.x * 2.0f + ImGui::GetFontSize(); ImGui::SetNextItemWidth(portWidth); if (ImGui::BeginCombo("Port", maple_ports[gamepad_port + 1])) @@ -1175,7 +1176,6 @@ static void controller_mapping_popup(const std::shared_ptr& gamep ImGui::EndCombo(); } portWidth += ImGui::CalcTextSize("Port").x + ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x; - ImGui::PopStyleVar(); } float comboWidth = ImGui::CalcTextSize("Dreamcast Controls").x + ImGui::GetStyle().ItemSpacing.x + ImGui::GetFontSize() + ImGui::GetStyle().FramePadding.x * 4; float gameConfigWidth = 0; @@ -1206,37 +1206,37 @@ static void controller_mapping_popup(const std::shared_ptr& gamep if (ImGui::Button("Reset...", ScaledVec2(100, 30))) ImGui::OpenPopup("Confirm Reset"); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ScaledVec2(20, 20)); - if (ImGui::BeginPopupModal("Confirm Reset", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { - ImGui::Text("Are you sure you want to reset the mappings to default?"); - static bool hitbox; - if (arcade_button_mode) + ImguiStyleVar _(ImGuiStyleVar_WindowPadding, ScaledVec2(20, 20)); + if (ImGui::BeginPopupModal("Confirm Reset", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { - ImGui::Text("Controller Type:"); - if (ImGui::RadioButton("Gamepad", !hitbox)) - hitbox = false; + ImGui::Text("Are you sure you want to reset the mappings to default?"); + static bool hitbox; + if (arcade_button_mode) + { + ImGui::Text("Controller Type:"); + if (ImGui::RadioButton("Gamepad", !hitbox)) + hitbox = false; + ImGui::SameLine(); + if (ImGui::RadioButton("Arcade / Hit Box", hitbox)) + hitbox = true; + } + ImGui::NewLine(); + ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ImVec2(20 * scaling, ImGui::GetStyle().ItemSpacing.y)); + ImguiStyleVar _1(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); + if (ImGui::Button("Yes")) + { + gamepad->resetMappingToDefault(arcade_button_mode, !hitbox); + gamepad->save_mapping(map_system); + ImGui::CloseCurrentPopup(); + } ImGui::SameLine(); - if (ImGui::RadioButton("Arcade / Hit Box", hitbox)) - hitbox = true; - } - ImGui::NewLine(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20 * scaling, ImGui::GetStyle().ItemSpacing.y)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); - if (ImGui::Button("Yes")) - { - gamepad->resetMappingToDefault(arcade_button_mode, !hitbox); - gamepad->save_mapping(map_system); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button("No")) - ImGui::CloseCurrentPopup(); - ImGui::PopStyleVar(2); + if (ImGui::Button("No")) + ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); + ImGui::EndPopup(); + } } - ImGui::PopStyleVar(1); ImGui::SameLine(); @@ -1297,7 +1297,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep continue; } sprintf(key_id, "key_id%d", systemMapping->key); - ImGui::PushID(key_id); + ImguiID _(key_id); const char *game_btn_name = nullptr; if (arcade_button_mode) @@ -1336,7 +1336,6 @@ static void controller_mapping_popup(const std::shared_ptr& gamep unmapControl(input_mapping, gamepad_port, systemMapping->key); } ImGui::NextColumn(); - ImGui::PopID(); } ImGui::Columns(1, nullptr, false); scrollWhenDraggingOnVoid(); @@ -1346,7 +1345,6 @@ static void controller_mapping_popup(const std::shared_ptr& gamep error_popup(); ImGui::EndPopup(); } - ImGui::PopStyleVar(); } static void gamepadSettingsPopup(const std::shared_ptr& gamepad) @@ -1354,7 +1352,7 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) centerNextWindow(); ImGui::SetNextWindowSize(min(ImGui::GetIO().DisplaySize, ScaledVec2(450.f, 300.f))); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); + ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); if (ImGui::BeginPopupModal("Gamepad Settings", NULL, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { if (ImGui::Button("Done", ScaledVec2(100, 30))) @@ -1432,7 +1430,6 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) ImGui::EndPopup(); } - ImGui::PopStyleVar(); } void error_popup() @@ -1440,14 +1437,14 @@ void error_popup() if (!error_msg_shown && !error_msg.empty()) { ImVec2 padding = ScaledVec2(20, 20); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, padding); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, padding); + ImguiStyleVar _(ImGuiStyleVar_WindowPadding, padding); + ImguiStyleVar _1(ImGuiStyleVar_ItemSpacing, padding); ImGui::OpenPopup("Error"); if (ImGui::BeginPopupModal("Error", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) { ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 400.f * settings.display.uiScale); ImGui::TextWrapped("%s", error_msg.c_str()); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(16, 3)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(16, 3)); float currentwidth = ImGui::GetContentRegionAvail().x; ImGui::SetCursorPosX((currentwidth - 80.f * settings.display.uiScale) / 2.f + ImGui::GetStyle().WindowPadding.x); if (ImGui::Button("OK", ScaledVec2(80.f, 0))) @@ -1456,12 +1453,9 @@ void error_popup() ImGui::CloseCurrentPopup(); } ImGui::SetItemDefaultFocus(); - ImGui::PopStyleVar(); ImGui::PopTextWrapPos(); ImGui::EndPopup(); } - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); error_msg_shown = true; } } @@ -1477,7 +1471,7 @@ static void contentpath_warning_popup() { ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 400.f * settings.display.uiScale); ImGui::TextWrapped(" Scanned %d folders but no game can be found! ", scanner.empty_folders_scanned); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(16, 3)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(16, 3)); float currentwidth = ImGui::GetContentRegionAvail().x; ImGui::SetCursorPosX((currentwidth - 100.f * settings.display.uiScale) / 2.f + ImGui::GetStyle().WindowPadding.x - 55.f * settings.display.uiScale); if (ImGui::Button("Reselect", ScaledVec2(100.f, 0))) @@ -1497,7 +1491,6 @@ static void contentpath_warning_popup() config::ContentPath.get().clear(); } ImGui::SetItemDefaultFocus(); - ImGui::PopStyleVar(); ImGui::EndPopup(); } } @@ -1519,12 +1512,11 @@ static void contentpath_warning_popup() } } -static inline void gui_debug_tab() +static void gui_debug_tab(const ImVec2& normal_padding) { if (ImGui::BeginTabItem(ICON_FA_BUG " Debug")) { - ImVec2 normal_padding = ImGui::GetStyle().FramePadding; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); header("Logging"); { LogManager *logManager = LogManager::GetInstance(); @@ -1576,7 +1568,6 @@ static inline void gui_debug_tab() } } #endif - ImGui::PopStyleVar(); ImGui::EndTabItem(); } } @@ -1601,7 +1592,7 @@ static void gui_display_settings() static bool maple_devices_changed; fullScreenWindow(false); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); + ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); ImGui::Begin("Settings", NULL, ImGuiWindowFlags_DragScrolling | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse); @@ -1632,7 +1623,7 @@ static void gui_display_settings() if (game_started) { ImGui::SameLine(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(16 * settings.display.uiScale, normal_padding.y)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ImVec2(16 * settings.display.uiScale, normal_padding.y)); if (config::Settings::instance().hasPerGameConfig()) { if (ImGui::Button("Delete Game Config", ScaledVec2(0, 30))) @@ -1647,7 +1638,6 @@ static void gui_display_settings() if (ImGui::Button("Make Game Config", ScaledVec2(0, 30))) config::Settings::instance().setPerGameConfig(true); } - ImGui::PopStyleVar(); } if (ImGui::GetContentRegionAvail().x / settings.display.uiScale >= 650.f) @@ -1660,7 +1650,7 @@ static void gui_display_settings() { if (ImGui::BeginTabItem(ICON_FA_TOOLBOX " General")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); { DisabledScope scope(settings.platform.isArcade()); @@ -1713,15 +1703,14 @@ static void gui_display_settings() int to_delete = -1; for (u32 i = 0; i < config::ContentPath.get().size(); i++) { - ImGui::PushID(config::ContentPath.get()[i].c_str()); + ImguiID _(config::ContentPath.get()[i].c_str()); ImGui::AlignTextToFramePadding(); ImGui::Text("%s", config::ContentPath.get()[i].c_str()); ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("X").x - ImGui::GetStyle().FramePadding.x); if (ImGui::Button("X")) to_delete = i; - ImGui::PopID(); } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(24, 3)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(24, 3)); #ifdef __ANDROID__ if (ImGui::Button("Add")) { @@ -1742,7 +1731,6 @@ static void gui_display_settings() ImGui::SameLine(); if (ImGui::Button("Rescan Content")) scanner.refresh(); - ImGui::PopStyleVar(); scrollWhenDraggingOnVoid(); ImGui::EndListBox(); @@ -1870,12 +1858,11 @@ static void gui_display_settings() ImGui::Unindent(); } #endif - ImGui::PopStyleVar(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ICON_FA_GAMEPAD " Controls")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); header("Physical Devices"); { if (ImGui::BeginTable("physicalDevices", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) @@ -1913,7 +1900,7 @@ static void gui_display_settings() ImGui::TableSetColumnIndex(2); char port_name[32]; sprintf(port_name, "##mapleport%d", i); - ImGui::PushID(port_name); + ImguiID _(port_name); ImGui::SetNextItemWidth(portComboWidth); if (ImGui::BeginCombo(port_name, maple_ports[gamepad->maple_port() + 1])) { @@ -1960,7 +1947,6 @@ static void gui_display_settings() ImGui::OpenPopup("Gamepad Settings"); gamepadSettingsPopup(gamepad); } - ImGui::PopID(); } ImGui::EndTable(); } @@ -2026,7 +2012,7 @@ static void gui_display_settings() { ImGui::TableSetColumnIndex(2 + port); sprintf(device_name, "##device%d.%d", bus, port + 1); - ImGui::PushID(device_name); + ImguiID _(device_name); ImGui::SetNextItemWidth(expComboWidth); if (ImGui::BeginCombo(device_name, maple_expansion_device_name(config::MapleExpansionDevices[bus][port]), ImGuiComboFlags_None)) { @@ -2043,13 +2029,12 @@ static void gui_display_settings() } ImGui::EndCombo(); } - ImGui::PopID(); } if (config::MapleMainDevices[bus] == MDT_LightGun) { ImGui::TableSetColumnIndex(3); sprintf(device_name, "##device%d.xhair", bus); - ImGui::PushID(device_name); + ImguiID _(device_name); u32 color = config::CrosshairColor[bus]; float xhairColor[4] { (color & 0xff) / 255.f, @@ -2078,7 +2063,6 @@ static void gui_display_settings() } } is_there_any_xhair |= enabled; - ImGui::PopID(); } ImGui::PopItemWidth(); } @@ -2091,7 +2075,6 @@ static void gui_display_settings() OptionCheckbox("Per Game VMU A1", config::PerGameVmu, "When enabled, each game has its own VMU on port 1 of controller A."); } - ImGui::PopStyleVar(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ICON_FA_DISPLAY " Video")) @@ -2131,7 +2114,7 @@ static void gui_display_settings() break; } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); constexpr int apiCount = 0 #ifdef USE_VULKAN @@ -2480,7 +2463,6 @@ static void gui_display_settings() } } #endif - ImGui::PopStyleVar(); ImGui::EndTabItem(); switch (renderApi) @@ -2501,7 +2483,7 @@ static void gui_display_settings() } if (ImGui::BeginTabItem(ICON_FA_MUSIC " Audio")) // or ICON_FA_VOLUME_OFF? { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); OptionCheckbox("Enable DSP", config::DSPEnabled, "Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms"); OptionCheckbox("Enable VMU Sounds", config::VmuSound, "Play VMU beeps when enabled."); @@ -2610,13 +2592,12 @@ static void gui_display_settings() } } - ImGui::PopStyleVar(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ICON_FA_WIFI " Network")) { ImGuiStyle& style = ImGui::GetStyle(); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); header("Network Type"); { @@ -2753,12 +2734,11 @@ static void gui_display_settings() OptionRadioButton("3 (Deluxe)", config::MultiboardSlaves, 2, "Three screens configuration"); } #endif - ImGui::PopStyleVar(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ICON_FA_MICROCHIP " Advanced")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); header("CPU Mode"); { ImGui::Columns(2, "cpu_modes", false); @@ -2816,17 +2796,16 @@ static void gui_display_settings() ShowHelpMarker("Specify lua filename to use. Should be located in Flycast config directory. Defaults to flycast.lua when empty."); } #endif - ImGui::PopStyleVar(); ImGui::EndTabItem(); } #if !defined(NDEBUG) || defined(DEBUGFAST) || FC_PROFILER - gui_debug_tab(); + gui_debug_tab(normal_padding); #endif if (ImGui::BeginTabItem(ICON_FA_CIRCLE_INFO " About")) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, normal_padding); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); header("Flycast"); { ImGui::Text("Version: %s", GIT_VERSION); @@ -2892,7 +2871,7 @@ static void gui_display_settings() #if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 && USE_VULKAN if (isVulkan(config::RendererType)) { - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); if (config::CustomGpuDriver) { std::string name, description, vendor, version; @@ -2934,13 +2913,13 @@ static void gui_display_settings() driverDirty = false; } - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ScaledVec2(20, 20)); + ImguiStyleVar _1(ImGuiStyleVar_WindowPadding, ScaledVec2(20, 20)); if (ImGui::BeginPopupModal("Reset Vulkan", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) { ImGui::Text("Do you want to reset Vulkan to use new driver?"); ImGui::NewLine(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(20 * settings.display.uiScale, ImGui::GetStyle().ItemSpacing.y)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); + ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ImVec2(20 * settings.display.uiScale, ImGui::GetStyle().ItemSpacing.y)); + ImguiStyleVar _1(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); if (ImGui::Button("Yes")) { mainui_reinit(); @@ -2949,16 +2928,10 @@ static void gui_display_settings() ImGui::SameLine(); if (ImGui::Button("No")) ImGui::CloseCurrentPopup(); - ImGui::PopStyleVar(2); - ImGui::EndPopup(); } - ImGui::PopStyleVar(); - - ImGui::PopStyleVar(); } #endif - ImGui::PopStyleVar(); ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -2968,14 +2941,19 @@ static void gui_display_settings() scrollWhenDraggingOnVoid(); windowDragScroll(); ImGui::End(); - ImGui::PopStyleVar(); } void gui_display_notification(const char *msg, int duration) { - std::lock_guard lock(osd_message_mutex); - osd_message = msg; - osd_message_end = getTimeMs() + duration; + if (gui_state != GuiState::Closed) + { + std::lock_guard _{osd_message_mutex}; + osd_message = msg; + osd_message_end = getTimeMs() + duration; + } + else { + toast.show(msg, "", duration); + } } static std::string get_notification() @@ -3014,8 +2992,8 @@ static bool gameImageButton(ImguiTexture& texture, const std::string& tooltip, I static void gui_display_content() { fullScreenWindow(false); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); + ImguiStyleVar _1(ImGuiStyleVar_WindowBorderSize, 0); ImGui::Begin("##main", NULL, ImGuiWindowFlags_NoDecoration); @@ -3073,7 +3051,7 @@ static void gui_display_content() int counter = 0; if (gui_state != GuiState::SelectDisk && filter.PassFilter("Dreamcast BIOS")) { - ImGui::PushID("bios"); + ImguiID _("bios"); bool pressed; if (config::BoxartDisplayMode) { @@ -3088,7 +3066,6 @@ static void gui_display_content() } if (pressed) gui_start_game(""); - ImGui::PopID(); counter++; } { @@ -3112,7 +3089,7 @@ static void gui_display_content() } if (filter.PassFilter(gameName.c_str())) { - ImGui::PushID(game.path.c_str()); + ImguiID _(game.path.c_str()); bool pressed = false; if (config::BoxartDisplayMode) { @@ -3158,7 +3135,6 @@ static void gui_display_content() break; } } - ImGui::PopID(); } } scanner.get_mutex().unlock(); @@ -3169,8 +3145,6 @@ static void gui_display_content() windowDragScroll(); ImGui::EndChild(); ImGui::End(); - ImGui::PopStyleVar(); - ImGui::PopStyleVar(); contentpath_warning_popup(); } @@ -3243,7 +3217,7 @@ static void gui_network_start() ImGui::Begin("##network", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); ImGui::AlignTextToFramePadding(); ImGui::SetCursorPosX(20.f * settings.display.uiScale); @@ -3280,8 +3254,6 @@ static void gui_network_start() } gui_stop_game(); } - ImGui::PopStyleVar(); - ImGui::End(); if ((kcode[0] & DC_BTN_START) == 0 && NetworkHandshake::instance != nullptr) @@ -3295,7 +3267,7 @@ static void gui_display_loadscreen() ImGui::Begin("##loading", NULL, ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); ImGui::AlignTextToFramePadding(); ImGui::SetCursorPosX(20.f * settings.display.uiScale); try { @@ -3324,9 +3296,10 @@ static void gui_display_loadscreen() else { ImGui::Text("%s", label); - ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.557f, 0.268f, 0.965f, 1.f)); - ImGui::ProgressBar(gameLoader.getProgress().progress, ImVec2(-1, 20.f * settings.display.uiScale), ""); - ImGui::PopStyleColor(); + { + ImguiStyleColor _(ImGuiCol_PlotHistogram, ImVec4(0.557f, 0.268f, 0.965f, 1.f)); + ImGui::ProgressBar(gameLoader.getProgress().progress, ImVec2(-1, 20.f * settings.display.uiScale), ""); + } float currentwidth = ImGui::GetContentRegionAvail().x; ImGui::SetCursorPosX((currentwidth - 100.f * settings.display.uiScale) / 2.f + ImGui::GetStyle().WindowPadding.x); @@ -3341,8 +3314,6 @@ static void gui_display_loadscreen() #endif gui_stop_game(ex.what()); } - ImGui::PopStyleVar(); - ImGui::End(); } @@ -3438,7 +3409,7 @@ static std::string getFPSNotification() } if (fps >= 0.f && fps < 9999.f) { char text[32]; - snprintf(text, sizeof(text), "F:%.1f%s", fps, settings.input.fastForwardMode ? " >>" : ""); + snprintf(text, sizeof(text), "F:%4.1f%s", fps, settings.input.fastForwardMode ? " >>" : ""); return std::string(text); } @@ -3450,32 +3421,34 @@ void gui_display_osd() { if (gui_state == GuiState::VJoyEdit) return; - std::string message = get_notification(); - if (message.empty()) - message = getFPSNotification(); - gui_newFrame(); ImGui::NewFrame(); #ifdef USE_RACHIEVEMENTS if (!achievements::notifier.draw()) #endif - if (!message.empty()) + if (!toast.draw()) { - ImGui::SetNextWindowBgAlpha(0); - ImGui::SetNextWindowPos(ImVec2(0, ImGui::GetIO().DisplaySize.y), ImGuiCond_Always, ImVec2(0.f, 1.f)); // Lower left corner - ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x, 0)); - - ImGui::Begin("##osd", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav - | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoBackground); - ImGui::PushFont(largeFont); - ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", message.c_str()); - ImGui::PopFont(); - ImGui::End(); + std::string message = getFPSNotification(); + if (!message.empty()) + { + const float maxW = uiScaled(640.f); + ImDrawList *dl = ImGui::GetForegroundDrawList(); + const ScaledVec2 padding(5.f, 5.f); + const ImVec2 size = largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, maxW, &message.front(), &message.back() + 1) + + padding * 2.f; + ImVec2 pos(0, ImGui::GetIO().DisplaySize.y - size.y); + constexpr float alpha = 0.7f; + const ImU32 bg_col = alphaOverride(0x00202020, alpha / 2.f); + dl->AddRectFilled(pos, pos + size, bg_col, 0.f); + pos += padding; + const ImU32 col = alphaOverride(0x0000FFFF, alpha); + dl->AddText(largeFont, largeFont->FontSize, pos, col, &message.front(), &message.back() + 1, maxW); + } } - imguiDriver->displayCrosshairs(); + imguiDriver->displayCrosshairs(); // OpenGL only if (config::FloatVMUs) - imguiDriver->displayVmus(); + imguiDriver->displayVmus(); // OpenGL only if (ggpo::active()) { @@ -3497,22 +3470,22 @@ void gui_display_profiler() ImGui::Begin("Profiler", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.8f, 1.0f)); - - std::unique_lock lock(fc_profiler::ProfileThread::s_allThreadsLock); - - for(const fc_profiler::ProfileThread* profileThread : fc_profiler::ProfileThread::s_allThreads) { - char text[256]; - std::snprintf(text, 256, "%.3f : Thread %s", (float)profileThread->cachedTime, profileThread->threadName.c_str()); - ImGui::TreeNode(text); + ImguiStyleColor _(ImGuiCol_Text, ImVec4(0.8f, 0.8f, 0.8f, 1.0f)); - ImGui::Indent(); - fc_profiler::drawGUI(profileThread->cachedResultTree); - ImGui::Unindent(); + std::unique_lock lock(fc_profiler::ProfileThread::s_allThreadsLock); + + for(const fc_profiler::ProfileThread* profileThread : fc_profiler::ProfileThread::s_allThreads) + { + char text[256]; + std::snprintf(text, 256, "%.3f : Thread %s", (float)profileThread->cachedTime, profileThread->threadName.c_str()); + ImGui::TreeNode(text); + + ImGui::Indent(); + fc_profiler::drawGUI(profileThread->cachedResultTree); + ImGui::Unindent(); + } } - - ImGui::PopStyleColor(); for (const fc_profiler::ProfileThread* profileThread : fc_profiler::ProfileThread::s_allThreads) { diff --git a/core/rend/gui_achievements.cpp b/core/rend/gui_achievements.cpp index 8facd22ac..f6a86f7cf 100644 --- a/core/rend/gui_achievements.cpp +++ b/core/rend/gui_achievements.cpp @@ -217,9 +217,10 @@ void achievementList() std::stringstream ss; ss << "You have unlocked " << game.unlockedAchievements << " of " << game.totalAchievements << " achievements and " << game.points << " of " << game.totalPoints << " points."; - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); - ImGui::TextWrapped("%s", ss.str().c_str()); - ImGui::PopStyleColor(); + { + ImguiStyleColor _(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); + ImGui::TextWrapped("%s", ss.str().c_str()); + } if (settings.raHardcoreMode) ImGui::Text("Hardcore Mode"); ImGui::EndChild(); @@ -260,10 +261,11 @@ void achievementList() ImGui::Text("%s", ach.title.c_str()); ImGui::PopFont(); - ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); - ImGui::TextWrapped("%s", ach.description.c_str()); - ImGui::TextWrapped("%s", ach.status.c_str()); - ImGui::PopStyleColor(); + { + ImguiStyleColor _(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); + ImGui::TextWrapped("%s", ach.description.c_str()); + ImGui::TextWrapped("%s", ach.status.c_str()); + } scrollWhenDraggingOnVoid(); ImGui::EndChild(); diff --git a/core/rend/gui_chat.h b/core/rend/gui_chat.h index 093c78c62..04fd4509e 100644 --- a/core/rend/gui_chat.h +++ b/core/rend/gui_chat.h @@ -78,8 +78,8 @@ public: if (!visible) return; - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); + ImguiStyleVar _1(ImGuiStyleVar_WindowBorderSize, 0); ImGui::SetNextWindowPos(ImVec2(settings.display.width / 2, settings.display.height) - ScaledVec2(200.f, 220.f), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ScaledVec2(400, 220), ImGuiCond_FirstUseEver); ImGui::SetNextWindowBgAlpha(0.7f); @@ -121,7 +121,6 @@ public: ImGui::SetItemDefaultFocus(); } ImGui::End(); - ImGui::PopStyleVar(2); } void receive(int playerNum, const std::string& msg) diff --git a/core/rend/gui_cheats.cpp b/core/rend/gui_cheats.cpp index db88e24dd..f163789d9 100644 --- a/core/rend/gui_cheats.cpp +++ b/core/rend/gui_cheats.cpp @@ -36,30 +36,31 @@ static void addCheat() ImGui::Begin("##main", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); - ImGui::AlignTextToFramePadding(); - ImGui::Indent(10 * settings.display.uiScale); - ImGui::Text("ADD CHEAT"); + { + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); + ImGui::AlignTextToFramePadding(); + ImGui::Indent(10 * settings.display.uiScale); + ImGui::Text("ADD CHEAT"); - ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Cancel").x - ImGui::GetStyle().FramePadding.x * 4.f - - ImGui::CalcTextSize("OK").x - ImGui::GetStyle().ItemSpacing.x); - if (ImGui::Button("Cancel")) - addingCheat = false; - ImGui::SameLine(); - if (ImGui::Button("OK")) - { - try { - cheatManager.addGameSharkCheat(cheatName, cheatCode); + ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Cancel").x - ImGui::GetStyle().FramePadding.x * 4.f + - ImGui::CalcTextSize("OK").x - ImGui::GetStyle().ItemSpacing.x); + if (ImGui::Button("Cancel")) addingCheat = false; - cheatName[0] = 0; - cheatCode[0] = 0; - } catch (const FlycastException& e) { - gui_error(e.what()); + ImGui::SameLine(); + if (ImGui::Button("OK")) + { + try { + cheatManager.addGameSharkCheat(cheatName, cheatCode); + addingCheat = false; + cheatName[0] = 0; + cheatCode[0] = 0; + } catch (const FlycastException& e) { + gui_error(e.what()); + } } - } - ImGui::Unindent(10 * settings.display.uiScale); - ImGui::PopStyleVar(); + ImGui::Unindent(10 * settings.display.uiScale); + } ImGui::BeginChild(ImGui::GetID("input"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_NavFlattened); { @@ -89,35 +90,36 @@ void gui_cheats() ImGui::Begin("##main", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); - ImGui::AlignTextToFramePadding(); - ImGui::Indent(10 * settings.display.uiScale); - ImGui::Text("CHEATS"); + { + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); + ImGui::AlignTextToFramePadding(); + ImGui::Indent(10 * settings.display.uiScale); + ImGui::Text("CHEATS"); - ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Add").x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().FramePadding.x * 6.f - - ImGui::CalcTextSize("Load").x - ImGui::GetStyle().ItemSpacing.x * 2); - if (ImGui::Button("Add")) - addingCheat = true; - ImGui::SameLine(); + ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Add").x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().FramePadding.x * 6.f + - ImGui::CalcTextSize("Load").x - ImGui::GetStyle().ItemSpacing.x * 2); + if (ImGui::Button("Add")) + addingCheat = true; + ImGui::SameLine(); #ifdef __ANDROID__ - if (ImGui::Button("Load")) - hostfs::addStorage(false, true, cheatFileSelected); + if (ImGui::Button("Load")) + hostfs::addStorage(false, true, cheatFileSelected); #else - if (ImGui::Button("Load")) - ImGui::OpenPopup("Select cheat file"); - select_file_popup("Select cheat file", [](bool cancelled, std::string selection) - { - cheatFileSelected(cancelled, selection); - return true; - }, true, "cht"); + if (ImGui::Button("Load")) + ImGui::OpenPopup("Select cheat file"); + select_file_popup("Select cheat file", [](bool cancelled, std::string selection) + { + cheatFileSelected(cancelled, selection); + return true; + }, true, "cht"); #endif - ImGui::SameLine(); - if (ImGui::Button("Close")) - gui_setState(GuiState::Commands); + ImGui::SameLine(); + if (ImGui::Button("Close")) + gui_setState(GuiState::Commands); - ImGui::Unindent(10 * settings.display.uiScale); - ImGui::PopStyleVar(); + ImGui::Unindent(10 * settings.display.uiScale); + } ImGui::BeginChild(ImGui::GetID("cheats"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_DragScrolling | ImGuiWindowFlags_NavFlattened); { @@ -126,11 +128,10 @@ void gui_cheats() else for (size_t i = 0; i < cheatManager.cheatCount(); i++) { - ImGui::PushID(("cheat" + std::to_string(i)).c_str()); + ImguiID _(("cheat" + std::to_string(i)).c_str()); bool v = cheatManager.cheatEnabled(i); if (ImGui::Checkbox(cheatManager.cheatDescription(i).c_str(), &v)) cheatManager.enableCheat(i, v); - ImGui::PopID(); } } scrollWhenDraggingOnVoid(); diff --git a/core/rend/gui_util.cpp b/core/rend/gui_util.cpp index 223b5663d..9d50584f3 100644 --- a/core/rend/gui_util.cpp +++ b/core/rend/gui_util.cpp @@ -28,6 +28,7 @@ #include "imgui_driver.h" #include "imgui.h" #include "imgui_internal.h" +#include "stdclass.h" static std::string select_current_directory = "**home**"; static std::vector subfolders; @@ -49,7 +50,7 @@ void select_file_popup(const char *prompt, StringCallback callback, bool selectFile, const std::string& selectExtension) { fullScreenWindow(true); - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); + ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); if (ImGui::BeginPopup(prompt, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize )) { @@ -105,49 +106,46 @@ void select_file_popup(const char *prompt, StringCallback callback, ImGui::Text("%s", title.c_str()); ImGui::BeginChild(ImGui::GetID("dir_list"), ImVec2(0, - 30 * settings.display.uiScale - ImGui::GetStyle().ItemSpacing.y), ImGuiChildFlags_Border, ImGuiWindowFlags_DragScrolling | ImGuiWindowFlags_NavFlattened); - - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ScaledVec2(8, 20)); - - if (!select_current_directory.empty() && select_current_directory != "/") { - if (ImGui::Selectable(".. Up to Parent Directory")) - { - subfolders_read = false; - select_current_directory = hostfs::storage().getParentPath(select_current_directory); - } - } + ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ScaledVec2(8, 20)); - for (const auto& entry : subfolders) - { - if (ImGui::Selectable(entry.name.c_str())) + if (!select_current_directory.empty() && select_current_directory != "/") { - subfolders_read = false; - select_current_directory = entry.path; + if (ImGui::Selectable(".. Up to Parent Directory")) + { + subfolders_read = false; + select_current_directory = hostfs::storage().getParentPath(select_current_directory); + } } - } - ImGui::PushStyleColor(ImGuiCol_Text, { 1, 1, 1, selectFile ? 1.f : 0.3f }); - for (const auto& entry : folderFiles) - { - if (selectFile) - { - if (ImGui::Selectable(entry.name.c_str())) - { - subfolders_read = false; - if (callback(false, entry.path)) - ImGui::CloseCurrentPopup(); - } - } - else - { - ImGui::Text("%s", entry.name.c_str()); - } - } - ImGui::PopStyleColor(); - - scrollWhenDraggingOnVoid(); - windowDragScroll(); - ImGui::PopStyleVar(); + for (const auto& entry : subfolders) + { + if (ImGui::Selectable(entry.name.c_str())) + { + subfolders_read = false; + select_current_directory = entry.path; + } + } + ImguiStyleColor _1(ImGuiCol_Text, { 1, 1, 1, selectFile ? 1.f : 0.3f }); + for (const auto& entry : folderFiles) + { + if (selectFile) + { + if (ImGui::Selectable(entry.name.c_str())) + { + subfolders_read = false; + if (callback(false, entry.path)) + ImGui::CloseCurrentPopup(); + } + } + else + { + ImGui::Text("%s", entry.name.c_str()); + } + } + scrollWhenDraggingOnVoid(); + windowDragScroll(); + } ImGui::EndChild(); if (!selectFile) { @@ -170,7 +168,6 @@ void select_file_popup(const char *prompt, StringCallback callback, error_popup(); ImGui::EndPopup(); } - ImGui::PopStyleVar(); } // See https://github.com/ocornut/imgui/issues/3379 @@ -518,25 +515,24 @@ template bool OptionSlider(const char *name, config::Option& option, bool OptionArrowButtons(const char *name, config::Option& option, int min, int max, const char *help, const char *format) { const float innerSpacing = ImGui::GetStyle().ItemInnerSpacing.x; - ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left - ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_FrameBg]); - float width = ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f; - std::string id = "##" + std::string(name); - ImGui::PushStyleVar(ImGuiStyleVar_DisabledAlpha, 1.0f); - ImGui::BeginDisabled(); - int size = snprintf(nullptr, 0, format, (int)option); - std::string value; - if (size >= 0) + const std::string id = "##" + std::string(name); { - value.resize(size + 1); - snprintf(value.data(), size + 1, format, (int)option); - value.resize(size); + ImguiStyleVar _(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)); // Left + ImguiStyleColor _1(ImGuiCol_Button, ImGui::GetStyle().Colors[ImGuiCol_FrameBg]); + const float width = ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f; + ImguiStyleVar _2(ImGuiStyleVar_DisabledAlpha, 1.0f); + ImGui::BeginDisabled(); + int size = snprintf(nullptr, 0, format, (int)option); + std::string value; + if (size >= 0) + { + value.resize(size + 1); + snprintf(value.data(), size + 1, format, (int)option); + value.resize(size); + } + ImGui::ButtonEx((value + id).c_str(), ImVec2(width, 0)); + ImGui::EndDisabled(); } - ImGui::ButtonEx((value + id).c_str(), ImVec2(width, 0)); - ImGui::EndDisabled(); - ImGui::PopStyleVar(); - ImGui::PopStyleColor(); - ImGui::PopStyleVar(); ImGui::SameLine(0.0f, innerSpacing); ImGui::PushButtonRepeat(true); @@ -612,8 +608,8 @@ void fullScreenWindow(bool modal) { if (!modal) { - ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); - ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); + ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); + ImguiStyleVar _1(ImGuiStyleVar_WindowBorderSize, 0); if (insetLeft > 0) { @@ -643,7 +639,6 @@ void fullScreenWindow(bool modal) ImGui::Begin("##insetBottom", NULL, ImGuiWindowFlags_NoDecoration); ImGui::End(); } - ImGui::PopStyleVar(2); } ImGui::SetNextWindowPos(ImVec2(insetLeft, insetTop)); ImGui::SetNextWindowSize(ImVec2(ImGui::GetIO().DisplaySize.x - insetLeft - insetRight, ImGui::GetIO().DisplaySize.y - insetTop - insetBottom)); @@ -790,3 +785,68 @@ bool BeginListBox(const char* label, const ImVec2& size_arg, ImGuiWindowFlags wi BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle, windowFlags); return true; } + +void Toast::show(const std::string& title, const std::string& message, u32 durationMs) +{ + const u64 now = getTimeMs(); + std::lock_guard _{mutex}; + // no start anim if still visible + if (now > endTime + END_ANIM_TIME) + startTime = getTimeMs(); + endTime = now + durationMs; + this->title = title; + this->message = message; +} + +bool Toast::draw() +{ + const u64 now = getTimeMs(); + std::lock_guard _{mutex}; + if (now > endTime + END_ANIM_TIME) { + title.clear(); + message.clear(); + } + if (title.empty() && message.empty()) + return false; + float alpha = 1.f; + if (now > endTime) + // Fade out + alpha = (std::cos((now - endTime) / (float)END_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; + + extern ImFont *largeFont; // FIXME + ImFont *regularFont = ImGui::GetFont(); + const float maxW = uiScaled(640.f); + const ImVec2 titleSize = title.empty() ? ImVec2() + : largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, maxW, &title.front(), &title.back() + 1); + const ImVec2 msgSize = message.empty() ? ImVec2() + : regularFont->CalcTextSizeA(regularFont->FontSize, FLT_MAX, maxW, &message.front(), &message.back() + 1); + const ScaledVec2 padding(10.f, 10.f); + const ScaledVec2 spacing(0.f, 5.f); + const ImVec2 totalSize = titleSize + spacing + msgSize + padding * 2.f; + + const ImVec2 displaySize(ImGui::GetIO().DisplaySize); + ImVec2 pos(0.f, displaySize.y - totalSize.y); + if (now - startTime < START_ANIM_TIME) + // Slide up + pos.y += totalSize.y * (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; + ImDrawList *dl = ImGui::GetForegroundDrawList(); + const ImU32 bg_col = alphaOverride(ImGui::GetColorU32(ImGuiCol_WindowBg), alpha / 2.f); + dl->AddRectFilled(pos, pos + totalSize, bg_col, 0.f); + const ImU32 col = alphaOverride(ImGui::GetColorU32(ImGuiCol_Border), alpha); + dl->AddRect(pos, pos + totalSize, col, 0.f); + + pos += padding; + if (!title.empty()) + { + const ImU32 col = alphaOverride(ImGui::GetColorU32(ImGuiCol_Text), alpha); + dl->AddText(largeFont, largeFont->FontSize, pos, col, &title.front(), &title.back() + 1, maxW); + pos += spacing + ImVec2(0.f, titleSize.y); + } + if (!message.empty()) + { + const ImU32 col = alphaOverride(0xFF00FFFF, alpha); // yellow + dl->AddText(regularFont, regularFont->FontSize, pos, col, &message.front(), &message.back() + 1, maxW); + } + + return true; +} diff --git a/core/rend/gui_util.h b/core/rend/gui_util.h index 1633410fb..26e7ad3fb 100644 --- a/core/rend/gui_util.h +++ b/core/rend/gui_util.h @@ -30,6 +30,7 @@ #include #include #include +#include typedef bool (*StringCallback)(bool cancelled, std::string selection); @@ -109,12 +110,16 @@ private: std::future future; }; +static inline float uiScaled(float f) { + return f * settings.display.uiScale; +} + struct ScaledVec2 : public ImVec2 { ScaledVec2() : ImVec2() {} ScaledVec2(float x, float y) - : ImVec2(x * settings.display.uiScale, y * settings.display.uiScale) {} + : ImVec2(uiScaled(x), uiScaled(y)) {} }; inline static ImVec2 min(const ImVec2& l, const ImVec2& r) { @@ -231,3 +236,24 @@ static inline float iconButtonWidth(const char *icon, const std::string& label) s.resize(sprintf(s.data(), "%s %s", icon, label.c_str())); return ImGui::CalcTextSize(s.c_str()).x + ImGui::GetStyle().FramePadding.x * 2; } + +static inline ImU32 alphaOverride(ImU32 color, float alpha) { + return (color & ~IM_COL32_A_MASK) | (IM_F32_TO_INT8_SAT(alpha) << IM_COL32_A_SHIFT); +} + +class Toast +{ +public: + void show(const std::string& title, const std::string& message, u32 durationMs); + bool draw(); + +private: + static constexpr u64 START_ANIM_TIME = 500; + static constexpr u64 END_ANIM_TIME = 1000; + + std::string title; + std::string message; + u64 startTime = 0; + u64 endTime = 0; + std::mutex mutex; +}; diff --git a/core/rend/imgui_driver.cpp b/core/rend/imgui_driver.cpp index b9435f468..91d81fbf2 100644 --- a/core/rend/imgui_driver.cpp +++ b/core/rend/imgui_driver.cpp @@ -17,10 +17,25 @@ along with Flycast. If not, see . */ #include "imgui_driver.h" +#include "gui_util.h" +#include "osd.h" #define STBI_ONLY_JPEG #define STBI_ONLY_PNG #include +constexpr float VMU_WIDTH = 96.f; +constexpr float VMU_HEIGHT = 64.f; +constexpr float VMU_PADDING = 8.f; + +void ImGuiDriver::reset() +{ + aspectRatios.clear(); + for (auto& tex : vmu_lcd_tex_ids) + tex = ImTextureID{}; + textureLoadCount = 0; + vmuLastChanged.fill({}); +} + static u8 *loadImage(const std::string& path, int& width, int& height) { FILE *file = nowide::fopen(path.c_str(), "rb"); @@ -34,7 +49,7 @@ static u8 *loadImage(const std::string& path, int& width, int& height) return imgData; } -ImTextureID ImGuiDriver::getOrLoadTexture(const std::string& path) +ImTextureID ImGuiDriver::getOrLoadTexture(const std::string& path, bool nearestSampling) { ImTextureID id = getTexture(path); if (id == ImTextureID() && textureLoadCount < 10) @@ -45,7 +60,7 @@ ImTextureID ImGuiDriver::getOrLoadTexture(const std::string& path) if (imgData != nullptr) { try { - id = updateTextureAndAspectRatio(path, imgData, width, height); + id = updateTextureAndAspectRatio(path, imgData, width, height, nearestSampling); } catch (...) { // vulkan can throw during resizing } @@ -54,3 +69,42 @@ ImTextureID ImGuiDriver::getOrLoadTexture(const std::string& path) } return id; } + +void ImGuiDriver::updateVmuTextures() +{ + for (int i = 0; i < 8; i++) + { + if (!vmu_lcd_status[i]) + continue; + + if (this->vmuLastChanged[i] != ::vmuLastChanged[i] || vmu_lcd_tex_ids[i] == ImTextureID()) + { + try { + vmu_lcd_tex_ids[i] = updateTexture("__vmu" + std::to_string(i), (const u8 *)vmu_lcd_data[i], 48, 32, true); + } catch (...) { + continue; + } + if (vmu_lcd_tex_ids[i] != ImTextureID()) + this->vmuLastChanged[i] = ::vmuLastChanged[i]; + } + } +} + +void ImGuiDriver::displayVmus(const ImVec2& pos) +{ + updateVmuTextures(); + const ScaledVec2 size(VMU_WIDTH, VMU_HEIGHT); + const float padding = uiScaled(VMU_PADDING); + ImDrawList *dl = ImGui::GetForegroundDrawList(); + ImVec2 cpos(pos + ScaledVec2(2.f, 0)); // 96 pixels wide + 2 * 2 -> 100 + for (int i = 0; i < 8; i++) + { + if (!vmu_lcd_status[i]) + continue; + + ImVec2 pos_b = cpos + size; + dl->AddImage(vmu_lcd_tex_ids[i], cpos, pos_b, ImVec2(0, 1), ImVec2(1, 0), 0x80ffffff); + cpos.y += size.y + padding; + } +} + diff --git a/core/rend/imgui_driver.h b/core/rend/imgui_driver.h index a84618e33..3a77a7f6e 100644 --- a/core/rend/imgui_driver.h +++ b/core/rend/imgui_driver.h @@ -21,6 +21,7 @@ #include "gui.h" #include #include +#include class ImGuiDriver { @@ -29,12 +30,15 @@ public: gui_initFonts(); } virtual ~ImGuiDriver() = default; + virtual void reset(); virtual void newFrame() = 0; virtual void renderDrawData(ImDrawData* drawData, bool gui_open) = 0; - virtual void displayVmus() {} - virtual void displayCrosshairs() {} + virtual void displayVmus() {} // TODO OpenGL only. Get rid of it + virtual void displayCrosshairs() {} // same + // draw all active vmus in a single column at the given position + void displayVmus(const ImVec2& pos); void doPresent() { textureLoadCount = 0; @@ -51,18 +55,22 @@ public: return 1; } - ImTextureID getOrLoadTexture(const std::string& path); + ImTextureID getOrLoadTexture(const std::string& path, bool nearestSampling = false); protected: virtual ImTextureID getTexture(const std::string& name) = 0; - virtual ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) = 0; + virtual ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) = 0; virtual void present() = 0; + void updateVmuTextures(); + + ImTextureID vmu_lcd_tex_ids[8] {}; + std::array vmuLastChanged {}; private: - ImTextureID updateTextureAndAspectRatio(const std::string& name, const u8 *data, int width, int height) + ImTextureID updateTextureAndAspectRatio(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) { textureLoadCount++; - ImTextureID textureId = updateTexture(name, data, width, height); + ImTextureID textureId = updateTexture(name, data, width, height, nearestSampling); if (textureId != ImTextureID()) aspectRatios[textureId] = (float)width / height; return textureId; diff --git a/core/rend/osd.cpp b/core/rend/osd.cpp index a4496551d..9722b92f4 100644 --- a/core/rend/osd.cpp +++ b/core/rend/osd.cpp @@ -175,7 +175,7 @@ u8 *loadOSDButtons(int &width, int &height) u32 vmu_lcd_data[8][48 * 32]; bool vmu_lcd_status[8]; -bool vmu_lcd_changed[8]; +u64 vmuLastChanged[8]; void push_vmu_screen(int bus_id, int bus_port, u8* buffer) { @@ -195,7 +195,7 @@ void push_vmu_screen(int bus_id, int bus_port, u8* buffer) #ifndef LIBRETRO vmu_lcd_status[vmu_id] = true; #endif - vmu_lcd_changed[vmu_id] = true; + vmuLastChanged[vmu_id] = getTimeMs(); } static const int lightgunCrosshairData[16 * 16] = diff --git a/core/rend/osd.h b/core/rend/osd.h index 8680189c9..b3a819d6d 100644 --- a/core/rend/osd.h +++ b/core/rend/osd.h @@ -37,7 +37,7 @@ void HideOSD(); // VMUs extern u32 vmu_lcd_data[8][48 * 32]; extern bool vmu_lcd_status[8]; -extern bool vmu_lcd_changed[8]; +extern u64 vmuLastChanged[8]; void push_vmu_screen(int bus_id, int bus_port, u8* buffer); @@ -59,5 +59,5 @@ static inline bool crosshairsNeeded() static inline void blankVmus() { memset(vmu_lcd_data, 0, sizeof(vmu_lcd_data)); - memset(vmu_lcd_changed, true, sizeof(vmu_lcd_changed)); + memset(vmuLastChanged, 0, sizeof(vmuLastChanged)); } diff --git a/core/rend/vulkan/overlay.cpp b/core/rend/vulkan/overlay.cpp index 0cbbdc74b..296df3c50 100644 --- a/core/rend/vulkan/overlay.cpp +++ b/core/rend/vulkan/overlay.cpp @@ -41,6 +41,7 @@ void VulkanOverlay::Init(QuadPipeline *pipeline) } xhairDrawer = std::make_unique(); xhairDrawer->Init(pipeline); + vmuLastChanged.fill({}); } void VulkanOverlay::Term() @@ -81,7 +82,7 @@ void VulkanOverlay::Prepare(vk::CommandBuffer cmdBuffer, bool vmu, bool crosshai } continue; } - if (texture != nullptr && !vmu_lcd_changed[i]) + if (texture != nullptr && ::vmuLastChanged[i] == this->vmuLastChanged[i]) continue; if (texture) @@ -90,7 +91,7 @@ void VulkanOverlay::Prepare(vk::CommandBuffer cmdBuffer, bool vmu, bool crosshai #ifdef VK_DEBUG VulkanContext::Instance()->setObjectName((VkImageView)texture->GetImageView(), vk::ImageView::objectType, "VMU " + std::to_string(i)); #endif - vmu_lcd_changed[i] = false; + this->vmuLastChanged[i] = ::vmuLastChanged[i]; } } if (crosshair && !xhairTexture) diff --git a/core/rend/vulkan/overlay.h b/core/rend/vulkan/overlay.h index fa0fab75f..ac222747f 100644 --- a/core/rend/vulkan/overlay.h +++ b/core/rend/vulkan/overlay.h @@ -45,6 +45,7 @@ private: std::array, 8> vmuTextures; std::vector commandBuffers; std::array, 8> drawers; + std::array vmuLastChanged {}; QuadPipeline *pipeline = nullptr; std::unique_ptr xhairTexture; diff --git a/core/rend/vulkan/vulkan_driver.h b/core/rend/vulkan/vulkan_driver.h index fcd726b5c..12c62ac34 100644 --- a/core/rend/vulkan/vulkan_driver.h +++ b/core/rend/vulkan/vulkan_driver.h @@ -26,10 +26,12 @@ class VulkanDriver final : public ImGuiDriver { public: - void reset() + void reset() override { + ImGuiDriver::reset(); textures.clear(); linearSampler.reset(); + pointSampler.reset(); ImGui_ImplVulkan_Shutdown(); justStarted = true; } @@ -51,20 +53,17 @@ public: bool rendering = context->IsRendering(); if (!rendering) context->NewFrame(); // may reset this driver - vk::CommandBuffer vmuCmdBuffer{}; if (!rendering || newFrameStarted) { - vmuCmdBuffer = getContext()->PrepareOverlay(true, false); context->BeginRenderPass(); context->PresentLastFrame(); - context->DrawOverlay(settings.display.uiScale, true, false); } if (!justStarted) // Record Imgui Draw Data and draw funcs into command buffer ImGui_ImplVulkan_RenderDrawData(drawData, (VkCommandBuffer)getCommandBuffer()); justStarted = false; if (!rendering || newFrameStarted) - context->EndFrame(vmuCmdBuffer); + context->EndFrame(); newFrameStarted = false; } catch (const InvalidVulkanContext& err) { } @@ -82,26 +81,47 @@ public: return ImTextureID{}; } - ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height) override + ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) override { VkTexture vkTex(std::make_unique()); vkTex.texture->tex_type = TextureType::_8888; vkTex.texture->SetCommandBuffer(getCommandBuffer()); vkTex.texture->UploadToGPU(width, height, data, false); vkTex.texture->SetCommandBuffer(nullptr); - if (!linearSampler) + VkSampler sampler; + if (nearestSampling) { - linearSampler = getContext()->GetDevice().createSamplerUnique( - vk::SamplerCreateInfo(vk::SamplerCreateFlags(), - vk::Filter::eLinear, vk::Filter::eLinear, - vk::SamplerMipmapMode::eLinear, - vk::SamplerAddressMode::eClampToBorder, - vk::SamplerAddressMode::eClampToBorder, - vk::SamplerAddressMode::eClampToEdge, 0.0f, false, - 0.f, false, vk::CompareOp::eNever, 0.0f, VK_LOD_CLAMP_NONE, - vk::BorderColor::eFloatTransparentBlack)); + if (!pointSampler) + { + pointSampler = getContext()->GetDevice().createSamplerUnique( + vk::SamplerCreateInfo(vk::SamplerCreateFlags(), + vk::Filter::eNearest, vk::Filter::eNearest, + vk::SamplerMipmapMode::eNearest, + vk::SamplerAddressMode::eClampToBorder, + vk::SamplerAddressMode::eClampToBorder, + vk::SamplerAddressMode::eClampToEdge, 0.0f, false, + 0.f, false, vk::CompareOp::eNever, 0.0f, VK_LOD_CLAMP_NONE, + vk::BorderColor::eFloatTransparentBlack)); + } + sampler = (VkSampler)*pointSampler; } - ImTextureID texId = vkTex.textureId = ImGui_ImplVulkan_AddTexture((VkSampler)*linearSampler, (VkImageView)vkTex.texture->GetImageView(), + else + { + if (!linearSampler) + { + linearSampler = getContext()->GetDevice().createSamplerUnique( + vk::SamplerCreateInfo(vk::SamplerCreateFlags(), + vk::Filter::eLinear, vk::Filter::eLinear, + vk::SamplerMipmapMode::eLinear, + vk::SamplerAddressMode::eClampToBorder, + vk::SamplerAddressMode::eClampToBorder, + vk::SamplerAddressMode::eClampToEdge, 0.0f, false, + 0.f, false, vk::CompareOp::eNever, 0.0f, VK_LOD_CLAMP_NONE, + vk::BorderColor::eFloatTransparentBlack)); + } + sampler = (VkSampler)*linearSampler; + } + ImTextureID texId = vkTex.textureId = ImGui_ImplVulkan_AddTexture(sampler, (VkImageView)vkTex.texture->GetImageView(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); // TODO update existing texture //auto it = textures.find(name); @@ -142,6 +162,7 @@ private: std::unordered_map textures; vk::UniqueSampler linearSampler; + vk::UniqueSampler pointSampler; bool newFrameStarted = false; bool justStarted = true; }; diff --git a/shell/libretro/libretro.cpp b/shell/libretro/libretro.cpp index 472401d79..a5af48220 100644 --- a/shell/libretro/libretro.cpp +++ b/shell/libretro/libretro.cpp @@ -1015,7 +1015,7 @@ static void update_variables(bool first_startup) | 0xff000000; vmu_lcd_status[i * 2] = false; - vmu_lcd_changed[i * 2] = true; + vmuLastChanged[i * 2] = getTimeMs(); vmu_screen_params[i].vmu_screen_position = UPPER_LEFT; vmu_screen_params[i].vmu_screen_size_mult = 1; vmu_screen_params[i].vmu_pixel_on_R = VMU_SCREEN_COLOR_MAP[VMU_DEFAULT_ON].r; From 6ab43096a267c8fa445f3581e8508887e76cf5d2 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 6 May 2024 21:54:55 +0200 Subject: [PATCH 62/86] ui: use uiScaled() --- core/lua/lua.cpp | 5 ++- core/network/ggpo.cpp | 8 ++-- core/rend/gui.cpp | 81 +++++++++++++++++----------------- core/rend/gui_achievements.cpp | 10 ++--- core/rend/gui_cheats.cpp | 8 ++-- core/rend/gui_util.cpp | 2 +- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/core/lua/lua.cpp b/core/lua/lua.cpp index 2957886c4..ffd899799 100644 --- a/core/lua/lua.cpp +++ b/core/lua/lua.cpp @@ -22,6 +22,7 @@ #include #include #include "rend/gui.h" +#include "rend/gui_util.h" #include "hw/mem/addrspace.h" #include "cfg/option.h" #include "emulator.h" @@ -369,7 +370,7 @@ static void beginWindow(const char *title, int x, int y, int w, int h) ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0); ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0); ImGui::SetNextWindowPos(ImVec2(x, y)); - ImGui::SetNextWindowSize(ImVec2(w * settings.display.uiScale, h * settings.display.uiScale)); + ImGui::SetNextWindowSize(ScaledVec2(w, h)); ImGui::SetNextWindowBgAlpha(0.7f); ImGui::Begin(title, NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoNavInputs | ImGuiWindowFlags_NoNavFocus); ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(0.557f, 0.268f, 0.965f, 1.f)); @@ -395,7 +396,7 @@ static void uiTextRightAligned(const std::string& text) static void uiBargraph(float v) { - ImGui::ProgressBar(v, ImVec2(-1, 10.f * settings.display.uiScale), ""); + ImGui::ProgressBar(v, ImVec2(-1, uiScaled(10.f)), ""); } static int uiButton(lua_State *L) diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp index 4a2a506ff..785d3bfbb 100644 --- a/core/network/ggpo.cpp +++ b/core/network/ggpo.cpp @@ -861,14 +861,14 @@ void displayStats() ImguiStyleVar _(ImGuiStyleVar_WindowRounding, 0); ImguiStyleVar _1(ImGuiStyleVar_WindowBorderSize, 0); ImGui::SetNextWindowPos(ImVec2(10, 10)); - ImGui::SetNextWindowSize(ImVec2(95 * settings.display.uiScale, 0)); + ImGui::SetNextWindowSize(ScaledVec2(95, 0)); ImGui::SetNextWindowBgAlpha(0.7f); ImGui::Begin("##ggpostats", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs); ImguiStyleColor _2(ImGuiCol_PlotHistogram, ImVec4(0.557f, 0.268f, 0.965f, 1.f)); // Send Queue ImGui::Text("Send Q"); - ImGui::ProgressBar(stats.network.send_queue_len / 10.f, ImVec2(-1, 10.f * settings.display.uiScale), ""); + ImGui::ProgressBar(stats.network.send_queue_len / 10.f, ImVec2(-1, uiScaled(10.f)), ""); // Frame Delay ImGui::Text("Delay"); @@ -890,7 +890,7 @@ void displayStats() // yellow ImGui::PushStyleColor(ImGuiCol_PlotHistogram, ImVec4(.9f, .9f, .1f, 1)); ImGui::Text("Predicted"); - ImGui::ProgressBar(stats.sync.predicted_frames / 7.f, ImVec2(-1, 10.f * settings.display.uiScale), ""); + ImGui::ProgressBar(stats.sync.predicted_frames / 7.f, ImVec2(-1, uiScaled(10.f)), ""); if (stats.sync.predicted_frames >= 5) ImGui::PopStyleColor(); @@ -899,7 +899,7 @@ void displayStats() if (timesync > 0) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1, 0, 0, 1)); ImGui::Text("Behind"); - ImGui::ProgressBar(0.5f + stats.timesync.local_frames_behind / 16.f, ImVec2(-1, 10.f * settings.display.uiScale), ""); + ImGui::ProgressBar(0.5f + stats.timesync.local_frames_behind / 16.f, ImVec2(-1, uiScaled(10.f)), ""); if (timesync > 0) { ImGui::PopStyleColor(); diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index a5edb5e24..508a2f38b 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -222,7 +222,7 @@ void gui_initFonts() ImGuiIO& io = ImGui::GetIO(); io.Fonts->Clear(); largeFont = nullptr; - const float fontSize = 17.f * settings.display.uiScale; + const float fontSize = uiScaled(17.f); size_t dataSize; std::unique_ptr data = resource::load("fonts/Roboto-Medium.ttf", dataSize); verify(data != nullptr); @@ -320,7 +320,7 @@ void gui_initFonts() // Large font without Asian glyphs data = resource::load("fonts/Roboto-Regular.ttf", dataSize); verify(data != nullptr); - const float largeFontSize = 21.f * settings.display.uiScale; + const float largeFontSize = uiScaled(21.f); largeFont = io.Fonts->AddFontFromMemoryTTF(data.release(), dataSize, largeFontSize, nullptr, ranges); NOTICE_LOG(RENDERER, "Screen DPI is %.0f, size %d x %d. Scaling by %.2f", settings.display.dpi, settings.display.width, settings.display.height, settings.display.uiScale); @@ -591,15 +591,15 @@ static void gui_display_commands() ImguiStyleVar _{ImGuiStyleVar_ButtonTextAlign, ImVec2(0.f, 0.5f)}; // left aligned float columnWidth = std::min(200.f, - (ImGui::GetContentRegionAvail().x - (100 + 150) * settings.display.uiScale - ImGui::GetStyle().FramePadding.x * 2) - / 2 / settings.display.uiScale); + (ImGui::GetContentRegionAvail().x - uiScaled(100 + 150) - ImGui::GetStyle().FramePadding.x * 2) + / 2 / uiScaled(1)); float buttonWidth = 150.f; // not scaled - bool lowW = ImGui::GetContentRegionAvail().x < ((100 + buttonWidth * 3) * settings.display.uiScale + bool lowW = ImGui::GetContentRegionAvail().x < (uiScaled(100 + buttonWidth * 3) + ImGui::GetStyle().FramePadding.x * 2 + ImGui::GetStyle().ItemSpacing.x * 2); if (lowW) buttonWidth = std::min(150.f, (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x * 2) - / 3 / settings.display.uiScale); + / 3 / uiScaled(1)); GameMedia game; game.path = settings.content.path; @@ -626,9 +626,9 @@ static void gui_display_commands() else { ImGui::Columns(4, "buttons", false); - ImGui::SetColumnWidth(0, 100.f * settings.display.uiScale + ImGui::GetStyle().ItemSpacing.x); - ImGui::SetColumnWidth(1, columnWidth * settings.display.uiScale); - ImGui::SetColumnWidth(2, columnWidth * settings.display.uiScale); + ImGui::SetColumnWidth(0, uiScaled(100.f) + ImGui::GetStyle().ItemSpacing.x); + ImGui::SetColumnWidth(1, uiScaled(columnWidth)); + ImGui::SetColumnWidth(2, uiScaled(columnWidth)); const ImVec2 vmuPos = ImGui::GetStyle().WindowPadding + ScaledVec2(0.f, 100.f) + ImVec2(insetLeft, ImGui::GetStyle().ItemSpacing.y); imguiDriver->displayVmus(vmuPos); @@ -708,7 +708,7 @@ static void gui_display_commands() SaveSettings(); } std::string slot = "Slot " + std::to_string((int)config::SavestateSlot + 1); - float spacingW = (buttonWidth * settings.display.uiScale - ImGui::GetFrameHeight() * 2 - ImGui::CalcTextSize(slot.c_str()).x) / 2; + float spacingW = (uiScaled(buttonWidth) - ImGui::GetFrameHeight() * 2 - ImGui::CalcTextSize(slot.c_str()).x) / 2; ImGui::SameLine(0, spacingW); ImGui::Text("%s", slot.c_str()); ImGui::SameLine(0, spacingW); @@ -1138,7 +1138,6 @@ static void controller_mapping_popup(const std::shared_ptr& gamep const float col_width = (winWidth - style.GrabMinSize - style.ItemSpacing.x - (ImGui::CalcTextSize("Map").x + style.FramePadding.x * 2.0f + style.ItemSpacing.x) - (ImGui::CalcTextSize("Unmap").x + style.FramePadding.x * 2.0f + style.ItemSpacing.x)) / 2; - const float scaling = settings.display.uiScale; static int map_system; static int item_current_map_idx = 0; @@ -1160,7 +1159,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep if (gamepad->maple_port() == MAPLE_PORTS) { ImGui::SameLine(); - ImguiStyleVar _(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, (30 * scaling - ImGui::GetFontSize()) / 2)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, (uiScaled(30) - ImGui::GetFontSize()) / 2)); portWidth = ImGui::CalcTextSize("AA").x + ImGui::GetStyle().ItemSpacing.x * 2.0f + ImGui::GetFontSize(); ImGui::SetNextItemWidth(portWidth); if (ImGui::BeginCombo("Port", maple_ports[gamepad_port + 1])) @@ -1181,7 +1180,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep float gameConfigWidth = 0; if (!settings.content.gameId.empty()) gameConfigWidth = ImGui::CalcTextSize(gamepad->isPerGameMapping() ? "Delete Game Config" : "Make Game Config").x + ImGui::GetStyle().ItemSpacing.x + ImGui::GetStyle().FramePadding.x * 2; - ImGui::SameLine(0, ImGui::GetContentRegionAvail().x - comboWidth - gameConfigWidth - ImGui::GetStyle().ItemSpacing.x - 100 * scaling * 2 - portWidth); + ImGui::SameLine(0, ImGui::GetContentRegionAvail().x - comboWidth - gameConfigWidth - ImGui::GetStyle().ItemSpacing.x - uiScaled(100) * 2 - portWidth); ImGui::AlignTextToFramePadding(); @@ -1222,7 +1221,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep hitbox = true; } ImGui::NewLine(); - ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ImVec2(20 * scaling, ImGui::GetStyle().ItemSpacing.y)); + ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ImVec2(uiScaled(20), ImGui::GetStyle().ItemSpacing.y)); ImguiStyleVar _1(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); if (ImGui::Button("Yes")) { @@ -1250,7 +1249,7 @@ static void controller_mapping_popup(const std::shared_ptr& gamep ImGui::SetNextItemWidth(comboWidth); // Make the combo height the same as the Done and Reset buttons - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, (30 * scaling - ImGui::GetFontSize()) / 2)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(ImGui::GetStyle().FramePadding.x, (uiScaled(30) - ImGui::GetFontSize()) / 2)); ImGui::Combo("##arcadeMode", &item_current_map_idx, items, IM_ARRAYSIZE(items)); ImGui::PopStyleVar(); if (last_item_current_map_idx != 2 && item_current_map_idx != last_item_current_map_idx) @@ -1404,7 +1403,7 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) { header("Rumble"); int power = gamepad->get_rumble_power(); - ImGui::SetNextItemWidth(300 * settings.display.uiScale); + ImGui::SetNextItemWidth(uiScaled(300)); if (ImGui::SliderInt("Power", &power, 0, 100, "%d%%")) gamepad->set_rumble_power(power); ImGui::SameLine(); @@ -1414,13 +1413,13 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) { header("Thumbsticks"); int deadzone = std::round(gamepad->get_dead_zone() * 100.f); - ImGui::SetNextItemWidth(300 * settings.display.uiScale); + ImGui::SetNextItemWidth(uiScaled(300)); if (ImGui::SliderInt("Dead zone", &deadzone, 0, 100, "%d%%")) gamepad->set_dead_zone(deadzone / 100.f); ImGui::SameLine(); ShowHelpMarker("Minimum deflection to register as input"); int saturation = std::round(gamepad->get_saturation() * 100.f); - ImGui::SetNextItemWidth(300 * settings.display.uiScale); + ImGui::SetNextItemWidth(uiScaled(300)); if (ImGui::SliderInt("Saturation", &saturation, 50, 200, "%d%%")) gamepad->set_saturation(saturation / 100.f); ImGui::SameLine(); @@ -1442,11 +1441,11 @@ void error_popup() ImGui::OpenPopup("Error"); if (ImGui::BeginPopupModal("Error", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) { - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 400.f * settings.display.uiScale); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + uiScaled(400.f)); ImGui::TextWrapped("%s", error_msg.c_str()); ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(16, 3)); float currentwidth = ImGui::GetContentRegionAvail().x; - ImGui::SetCursorPosX((currentwidth - 80.f * settings.display.uiScale) / 2.f + ImGui::GetStyle().WindowPadding.x); + ImGui::SetCursorPosX((currentwidth - uiScaled(80.f)) / 2.f + ImGui::GetStyle().WindowPadding.x); if (ImGui::Button("OK", ScaledVec2(80.f, 0))) { error_msg.clear(); @@ -1469,11 +1468,11 @@ static void contentpath_warning_popup() ImGui::OpenPopup("Incorrect Content Location?"); if (ImGui::BeginPopupModal("Incorrect Content Location?", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove)) { - ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + 400.f * settings.display.uiScale); + ImGui::PushTextWrapPos(ImGui::GetCursorPos().x + uiScaled(400.f)); ImGui::TextWrapped(" Scanned %d folders but no game can be found! ", scanner.empty_folders_scanned); ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(16, 3)); float currentwidth = ImGui::GetContentRegionAvail().x; - ImGui::SetCursorPosX((currentwidth - 100.f * settings.display.uiScale) / 2.f + ImGui::GetStyle().WindowPadding.x - 55.f * settings.display.uiScale); + ImGui::SetCursorPosX((currentwidth - uiScaled(100.f)) / 2.f + ImGui::GetStyle().WindowPadding.x - uiScaled(55.f)); if (ImGui::Button("Reselect", ScaledVec2(100.f, 0))) { scanner.content_path_looks_incorrect = false; @@ -1482,7 +1481,7 @@ static void contentpath_warning_popup() } ImGui::SameLine(); - ImGui::SetCursorPosX((currentwidth - 100.f * settings.display.uiScale) / 2.f + ImGui::GetStyle().WindowPadding.x + 55.f * settings.display.uiScale); + ImGui::SetCursorPosX((currentwidth - uiScaled(100.f)) / 2.f + ImGui::GetStyle().WindowPadding.x + uiScaled(55.f)); if (ImGui::Button("Cancel", ScaledVec2(100.f, 0))) { scanner.content_path_looks_incorrect = false; @@ -1623,7 +1622,7 @@ static void gui_display_settings() if (game_started) { ImGui::SameLine(); - ImguiStyleVar _(ImGuiStyleVar_FramePadding, ImVec2(16 * settings.display.uiScale, normal_padding.y)); + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ImVec2(uiScaled(16), normal_padding.y)); if (config::Settings::instance().hasPerGameConfig()) { if (ImGui::Button("Delete Game Config", ScaledVec2(0, 30))) @@ -1640,7 +1639,7 @@ static void gui_display_settings() } } - if (ImGui::GetContentRegionAvail().x / settings.display.uiScale >= 650.f) + if (ImGui::GetContentRegionAvail().x >= uiScaled(650.f)) ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(16, 6)); else // low width @@ -1917,7 +1916,7 @@ static void gui_display_settings() } ImGui::TableSetColumnIndex(3); - ImGui::SameLine(0, 8 * settings.display.uiScale); + ImGui::SameLine(0, uiScaled(8)); if (gamepad->remappable() && ImGui::Button("Map")) { gamepad_port = 0; @@ -1942,7 +1941,7 @@ static void gui_display_settings() #endif ) { - ImGui::SameLine(0, 16 * settings.display.uiScale); + ImGui::SameLine(0, uiScaled(16)); if (ImGui::Button("Settings")) ImGui::OpenPopup("Gamepad Settings"); gamepadSettingsPopup(gamepad); @@ -1963,7 +1962,7 @@ static void gui_display_settings() { bool is_there_any_xhair = false; if (ImGui::BeginTable("dreamcastDevices", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings, - ImVec2(0, 0), 8 * settings.display.uiScale)) + ImVec2(0, 0), uiScaled(8))) { const float mainComboWidth = calcComboWidth(maple_device_types[11]); // densha de go! controller const float expComboWidth = calcComboWidth(maple_expansion_device_types[2]); // vibration pack @@ -2918,7 +2917,7 @@ static void gui_display_settings() { ImGui::Text("Do you want to reset Vulkan to use new driver?"); ImGui::NewLine(); - ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ImVec2(20 * settings.display.uiScale, ImGui::GetStyle().ItemSpacing.y)); + ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ImVec2(uiScaled(20), ImGui::GetStyle().ItemSpacing.y)); ImguiStyleVar _1(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); if (ImGui::Button("Yes")) { @@ -2999,15 +2998,15 @@ static void gui_display_content() ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); ImGui::AlignTextToFramePadding(); - ImGui::Indent(10 * settings.display.uiScale); + ImGui::Indent(uiScaled(10)); ImGui::Text("GAMES"); - ImGui::Unindent(10 * settings.display.uiScale); + ImGui::Unindent(uiScaled(10)); static ImGuiTextFilter filter; const float settingsBtnW = iconButtonWidth(ICON_FA_GEAR, "Settings"); #if !defined(__ANDROID__) && !defined(TARGET_IPHONE) && !defined(TARGET_UWP) && !defined(__SWITCH__) - ImGui::SameLine(0, 32 * settings.display.uiScale); - filter.Draw("Filter", ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x - 32 * settings.display.uiScale + ImGui::SameLine(0, uiScaled(32)); + filter.Draw("Filter", ImGui::GetContentRegionAvail().x - ImGui::GetStyle().ItemSpacing.x - uiScaled(32) - settingsBtnW - ImGui::GetStyle().ItemSpacing.x); #endif if (gui_state != GuiState::SelectDisk) @@ -3039,7 +3038,7 @@ static void gui_display_content() ImGui::BeginChild(ImGui::GetID("library"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_DragScrolling | ImGuiWindowFlags_NavFlattened); { const float totalWidth = ImGui::GetContentRegionMax().x - (!ImGui::GetCurrentWindow()->ScrollbarY ? ImGui::GetStyle().ScrollbarSize : 0); - const int itemsPerLine = std::max(totalWidth / (150 * settings.display.uiScale + ImGui::GetStyle().ItemSpacing.x), 1); + const int itemsPerLine = std::max(totalWidth / (uiScaled(150) + ImGui::GetStyle().ItemSpacing.x), 1); const float responsiveBoxSize = totalWidth / itemsPerLine - ImGui::GetStyle().FramePadding.x * 2; const ImVec2 responsiveBoxVec2 = ImVec2(responsiveBoxSize, responsiveBoxSize); @@ -3219,7 +3218,7 @@ static void gui_network_start() ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); ImGui::AlignTextToFramePadding(); - ImGui::SetCursorPosX(20.f * settings.display.uiScale); + ImGui::SetCursorPosX(uiScaled(20.f)); if (networkStatus.wait_for(std::chrono::milliseconds(0)) == std::future_status::ready) { @@ -3242,8 +3241,8 @@ static void gui_network_start() ImGui::Text("%s", get_notification().c_str()); float currentwidth = ImGui::GetContentRegionAvail().x; - ImGui::SetCursorPosX((currentwidth - 100.f * settings.display.uiScale) / 2.f + ImGui::GetStyle().WindowPadding.x); - ImGui::SetCursorPosY(126.f * settings.display.uiScale); + ImGui::SetCursorPosX((currentwidth - uiScaled(100.f)) / 2.f + ImGui::GetStyle().WindowPadding.x); + ImGui::SetCursorPosY(uiScaled(126.f)); if (ImGui::Button("Cancel", ScaledVec2(100.f, 0)) && NetworkHandshake::instance != nullptr) { NetworkHandshake::instance->stop(); @@ -3269,7 +3268,7 @@ static void gui_display_loadscreen() ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); ImGui::AlignTextToFramePadding(); - ImGui::SetCursorPosX(20.f * settings.display.uiScale); + ImGui::SetCursorPosX(uiScaled(20.f)); try { const char *label = gameLoader.getProgress().label; if (label == nullptr) @@ -3298,12 +3297,12 @@ static void gui_display_loadscreen() ImGui::Text("%s", label); { ImguiStyleColor _(ImGuiCol_PlotHistogram, ImVec4(0.557f, 0.268f, 0.965f, 1.f)); - ImGui::ProgressBar(gameLoader.getProgress().progress, ImVec2(-1, 20.f * settings.display.uiScale), ""); + ImGui::ProgressBar(gameLoader.getProgress().progress, ImVec2(-1, uiScaled(20.f)), ""); } float currentwidth = ImGui::GetContentRegionAvail().x; - ImGui::SetCursorPosX((currentwidth - 100.f * settings.display.uiScale) / 2.f + ImGui::GetStyle().WindowPadding.x); - ImGui::SetCursorPosY(126.f * settings.display.uiScale); + ImGui::SetCursorPosX((currentwidth - uiScaled(100.f)) / 2.f + ImGui::GetStyle().WindowPadding.x); + ImGui::SetCursorPosY(uiScaled(126.f)); if (ImGui::Button("Cancel", ScaledVec2(100.f, 0))) gameLoader.cancel(); } diff --git a/core/rend/gui_achievements.cpp b/core/rend/gui_achievements.cpp index f6a86f7cf..e5d42fe63 100644 --- a/core/rend/gui_achievements.cpp +++ b/core/rend/gui_achievements.cpp @@ -133,7 +133,7 @@ bool Notification::draw() float y = ImGui::GetIO().DisplaySize.y; if (now - startTime < START_ANIM_TIME) // Slide up - y += 80.f * settings.display.uiScale * (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; + y += uiScaled(80.f) * (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; ImGui::SetNextWindowPos(ImVec2(0, y), ImGuiCond_Always, ImVec2(0.f, 1.f)); // Lower left corner if (type == Challenge) @@ -205,12 +205,12 @@ void achievementList() { float w = ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().ItemSpacing.x * 2 - ImGui::GetStyle().WindowPadding.x - - (80.f + 20.f * 2) * settings.display.uiScale; // image width and button frame padding + - uiScaled(80.f + 20.f * 2); // image width and button frame padding Game game = getCurrentGame(); ImguiTexture tex(game.image); tex.draw(ScaledVec2(80.f, 80.f)); ImGui::SameLine(); - ImGui::BeginChild("game_info", ImVec2(w, 80.f * settings.display.uiScale), ImGuiChildFlags_None, ImGuiWindowFlags_None); + ImGui::BeginChild("game_info", ImVec2(w, uiScaled(80.f)), ImGuiChildFlags_None, ImGuiWindowFlags_None); ImGui::PushFont(largeFont); ImGui::Text("%s", game.title.c_str()); ImGui::PopFont(); @@ -241,7 +241,7 @@ void achievementList() if (ach.category != category) { category = ach.category; - ImGui::Indent(10 * settings.display.uiScale); + ImGui::Indent(uiScaled(10)); if (category == "Locked" || category == "Active Challenges" || category == "Almost There") ImGui::Text(ICON_FA_LOCK); else if (category == "Unlocked" || category == "Recently Unlocked") @@ -250,7 +250,7 @@ void achievementList() ImGui::PushFont(largeFont); ImGui::Text("%s", category.c_str()); ImGui::PopFont(); - ImGui::Unindent(10 * settings.display.uiScale); + ImGui::Unindent(uiScaled(10)); } ImguiID _("achiev" + std::to_string(id++)); ImguiTexture tex(ach.image); diff --git a/core/rend/gui_cheats.cpp b/core/rend/gui_cheats.cpp index f163789d9..951905fbb 100644 --- a/core/rend/gui_cheats.cpp +++ b/core/rend/gui_cheats.cpp @@ -39,7 +39,7 @@ static void addCheat() { ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); ImGui::AlignTextToFramePadding(); - ImGui::Indent(10 * settings.display.uiScale); + ImGui::Indent(uiScaled(10)); ImGui::Text("ADD CHEAT"); ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Cancel").x - ImGui::GetStyle().FramePadding.x * 4.f @@ -59,7 +59,7 @@ static void addCheat() } } - ImGui::Unindent(10 * settings.display.uiScale); + ImGui::Unindent(uiScaled(10)); } ImGui::BeginChild(ImGui::GetID("input"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_NavFlattened); @@ -93,7 +93,7 @@ void gui_cheats() { ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); ImGui::AlignTextToFramePadding(); - ImGui::Indent(10 * settings.display.uiScale); + ImGui::Indent(uiScaled(10)); ImGui::Text("CHEATS"); ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Add").x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().FramePadding.x * 6.f @@ -118,7 +118,7 @@ void gui_cheats() if (ImGui::Button("Close")) gui_setState(GuiState::Commands); - ImGui::Unindent(10 * settings.display.uiScale); + ImGui::Unindent(uiScaled(10)); } ImGui::BeginChild(ImGui::GetID("cheats"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_DragScrolling | ImGuiWindowFlags_NavFlattened); diff --git a/core/rend/gui_util.cpp b/core/rend/gui_util.cpp index 9d50584f3..faf4316d7 100644 --- a/core/rend/gui_util.cpp +++ b/core/rend/gui_util.cpp @@ -104,7 +104,7 @@ void select_file_popup(const char *prompt, StringCallback callback, title = select_current_directory; ImGui::Text("%s", title.c_str()); - ImGui::BeginChild(ImGui::GetID("dir_list"), ImVec2(0, - 30 * settings.display.uiScale - ImGui::GetStyle().ItemSpacing.y), + ImGui::BeginChild(ImGui::GetID("dir_list"), ImVec2(0, - uiScaled(30) - ImGui::GetStyle().ItemSpacing.y), ImGuiChildFlags_Border, ImGuiWindowFlags_DragScrolling | ImGuiWindowFlags_NavFlattened); { ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ScaledVec2(8, 20)); From 763d9ce06ad7c29b733708ac025492d437572209 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 6 May 2024 22:26:09 +0200 Subject: [PATCH 63/86] ui: split display_settings --- core/rend/gui.cpp | 2635 +++++++++++++++++++++++---------------------- 1 file changed, 1332 insertions(+), 1303 deletions(-) diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index 508a2f38b..a6a0cef79 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -1511,64 +1511,59 @@ static void contentpath_warning_popup() } } -static void gui_debug_tab(const ImVec2& normal_padding) +static void gui_debug_tab() { - if (ImGui::BeginTabItem(ICON_FA_BUG " Debug")) + header("Logging"); { - ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); - header("Logging"); - { - LogManager *logManager = LogManager::GetInstance(); - for (LogTypes::LOG_TYPE type = LogTypes::AICA; type < LogTypes::NUMBER_OF_LOGS; type = (LogTypes::LOG_TYPE)(type + 1)) - { - bool enabled = logManager->IsEnabled(type, logManager->GetLogLevel()); - std::string name = std::string(logManager->GetShortName(type)) + " - " + logManager->GetFullName(type); - if (ImGui::Checkbox(name.c_str(), &enabled) && logManager->GetLogLevel() > LogTypes::LWARNING) { - logManager->SetEnable(type, enabled); - cfgSaveBool("log", logManager->GetShortName(type), enabled); + LogManager *logManager = LogManager::GetInstance(); + for (LogTypes::LOG_TYPE type = LogTypes::AICA; type < LogTypes::NUMBER_OF_LOGS; type = (LogTypes::LOG_TYPE)(type + 1)) + { + bool enabled = logManager->IsEnabled(type, logManager->GetLogLevel()); + std::string name = std::string(logManager->GetShortName(type)) + " - " + logManager->GetFullName(type); + if (ImGui::Checkbox(name.c_str(), &enabled) && logManager->GetLogLevel() > LogTypes::LWARNING) { + logManager->SetEnable(type, enabled); + cfgSaveBool("log", logManager->GetShortName(type), enabled); + } + } + ImGui::Spacing(); + + static const char *levels[] = { "Notice", "Error", "Warning", "Info", "Debug" }; + if (ImGui::BeginCombo("Log Verbosity", levels[logManager->GetLogLevel() - 1], ImGuiComboFlags_None)) + { + for (std::size_t i = 0; i < std::size(levels); i++) + { + bool is_selected = logManager->GetLogLevel() - 1 == (int)i; + if (ImGui::Selectable(levels[i], &is_selected)) { + logManager->SetLogLevel((LogTypes::LOG_LEVELS)(i + 1)); + cfgSaveInt("log", "Verbosity", i + 1); } - } - ImGui::Spacing(); - - static const char *levels[] = { "Notice", "Error", "Warning", "Info", "Debug" }; - if (ImGui::BeginCombo("Log Verbosity", levels[logManager->GetLogLevel() - 1], ImGuiComboFlags_None)) - { - for (std::size_t i = 0; i < std::size(levels); i++) - { - bool is_selected = logManager->GetLogLevel() - 1 == (int)i; - if (ImGui::Selectable(levels[i], &is_selected)) { - logManager->SetLogLevel((LogTypes::LOG_LEVELS)(i + 1)); - cfgSaveInt("log", "Verbosity", i + 1); - } - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - } -#if FC_PROFILER - ImGui::Spacing(); - header("Profiling"); - { - - OptionCheckbox("Enable", config::ProfilerEnabled, "Enable the profiler."); - if (!config::ProfilerEnabled) - { - ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); - ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + if (is_selected) + ImGui::SetItemDefaultFocus(); } - OptionCheckbox("Display", config::ProfilerDrawToGUI, "Draw the profiler output in an overlay."); - OptionCheckbox("Output to terminal", config::ProfilerOutputTTY, "Write the profiler output to the terminal"); - // TODO frame warning time - if (!config::ProfilerEnabled) - { - ImGui::PopItemFlag(); - ImGui::PopStyleVar(); - } - } -#endif - ImGui::EndTabItem(); + ImGui::EndCombo(); + } } +#if FC_PROFILER + ImGui::Spacing(); + header("Profiling"); + { + + OptionCheckbox("Enable", config::ProfilerEnabled, "Enable the profiler."); + if (!config::ProfilerEnabled) + { + ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true); + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + } + OptionCheckbox("Display", config::ProfilerDrawToGUI, "Draw the profiler output in an overlay."); + OptionCheckbox("Output to terminal", config::ProfilerOutputTTY, "Write the profiler output to the terminal"); + // TODO frame warning time + if (!config::ProfilerEnabled) + { + ImGui::PopItemFlag(); + ImGui::PopStyleVar(); + } + } +#endif } static void addContentPath(const std::string& path) @@ -1586,6 +1581,1276 @@ static float calcComboWidth(const char *biggestLabel) { return ImGui::CalcTextSize(biggestLabel).x + ImGui::GetStyle().FramePadding.x * 2.0f + ImGui::GetFrameHeight(); } +static void gui_settings_general() +{ + { + DisabledScope scope(settings.platform.isArcade()); + + const char *languages[] = { "Japanese", "English", "German", "French", "Spanish", "Italian", "Default" }; + OptionComboBox("Language", config::Language, languages, std::size(languages), + "The language as configured in the Dreamcast BIOS"); + + const char *broadcast[] = { "NTSC", "PAL", "PAL/M", "PAL/N", "Default" }; + OptionComboBox("Broadcast", config::Broadcast, broadcast, std::size(broadcast), + "TV broadcasting standard for non-VGA modes"); + } + + const char *consoleRegion[] = { "Japan", "USA", "Europe", "Default" }; + const char *arcadeRegion[] = { "Japan", "USA", "Export", "Korea" }; + const char **region = settings.platform.isArcade() ? arcadeRegion : consoleRegion; + OptionComboBox("Region", config::Region, region, std::size(consoleRegion), + "BIOS region"); + + const char *cable[] = { "VGA", "RGB Component", "TV Composite" }; + { + DisabledScope scope(config::Cable.isReadOnly() || settings.platform.isArcade()); + + const char *value = config::Cable == 0 ? cable[0] + : config::Cable > 0 && config::Cable <= (int)std::size(cable) ? cable[config::Cable - 1] + : "?"; + if (ImGui::BeginCombo("Cable", value, ImGuiComboFlags_None)) + { + for (int i = 0; i < IM_ARRAYSIZE(cable); i++) + { + bool is_selected = i == 0 ? config::Cable <= 1 : config::Cable - 1 == i; + if (ImGui::Selectable(cable[i], &is_selected)) + config::Cable = i == 0 ? 0 : i + 1; + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ShowHelpMarker("Video connection type"); + } + +#if !defined(TARGET_IPHONE) + ImVec2 size; + size.x = 0.0f; + size.y = (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().FramePadding.y * 2.f) + * (config::ContentPath.get().size() + 1) ;//+ ImGui::GetStyle().FramePadding.y * 2.f; + + if (BeginListBox("Content Location", size, ImGuiWindowFlags_NavFlattened)) + { + int to_delete = -1; + for (u32 i = 0; i < config::ContentPath.get().size(); i++) + { + ImguiID _(config::ContentPath.get()[i].c_str()); + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", config::ContentPath.get()[i].c_str()); + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("X").x - ImGui::GetStyle().FramePadding.x); + if (ImGui::Button("X")) + to_delete = i; + } + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(24, 3)); +#ifdef __ANDROID__ + if (ImGui::Button("Add")) + { + hostfs::addStorage(true, false, [](bool cancelled, std::string selection) { + if (!cancelled) + addContentPath(selection); + }); + } +#else + if (ImGui::Button("Add")) + ImGui::OpenPopup("Select Directory"); + select_file_popup("Select Directory", [](bool cancelled, std::string selection) { + if (!cancelled) + addContentPath(selection); + return true; + }); +#endif + ImGui::SameLine(); + if (ImGui::Button("Rescan Content")) + scanner.refresh(); + scrollWhenDraggingOnVoid(); + + ImGui::EndListBox(); + if (to_delete >= 0) + { + scanner.stop(); + config::ContentPath.get().erase(config::ContentPath.get().begin() + to_delete); + scanner.refresh(); + } + } + ImGui::SameLine(); + ShowHelpMarker("The directories where your games are stored"); + + size.y = ImGui::GetTextLineHeightWithSpacing() * 1.25f + ImGui::GetStyle().FramePadding.y * 2.0f; + +#if defined(__linux__) && !defined(__ANDROID__) + if (BeginListBox("Data Directory", size, ImGuiWindowFlags_NavFlattened)) + { + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", get_writable_data_path("").c_str()); + ImGui::EndListBox(); + } + ImGui::SameLine(); + ShowHelpMarker("The directory containing BIOS files, as well as saved VMUs and states"); +#else + if (BeginListBox("Home Directory", size, ImGuiWindowFlags_NavFlattened)) + { + ImGui::AlignTextToFramePadding(); + ImGui::Text("%s", get_writable_config_path("").c_str()); +#ifdef __ANDROID__ + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Change").x - ImGui::GetStyle().FramePadding.x); + if (ImGui::Button("Change")) + gui_setState(GuiState::Onboarding); +#endif +#ifdef TARGET_MAC + ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Reveal in Finder").x - ImGui::GetStyle().FramePadding.x); + if (ImGui::Button("Reveal in Finder")) + { + char temp[512]; + sprintf(temp, "open \"%s\"", get_writable_config_path("").c_str()); + system(temp); + } +#endif + ImGui::EndListBox(); + } + ImGui::SameLine(); + ShowHelpMarker("The directory where Flycast saves configuration files and VMUs. BIOS files should be in a subfolder named \"data\""); +#endif // !linux +#endif // !TARGET_IPHONE + + OptionCheckbox("Box Art Game List", config::BoxartDisplayMode, + "Display game cover art in the game list."); + OptionCheckbox("Fetch Box Art", config::FetchBoxart, + "Fetch cover images from TheGamesDB.net."); + if (OptionSlider("UI Scaling", config::UIScaling, 50, 200, "Adjust the size of UI elements and fonts.", "%d%%")) + uiUserScaleUpdated = true; + if (uiUserScaleUpdated) + { + ImGui::SameLine(); + if (ImGui::Button("Apply")) { + mainui_reinit(); + uiUserScaleUpdated = false; + } + } + + if (OptionCheckbox("Hide Legacy Naomi Roms", config::HideLegacyNaomiRoms, + "Hide .bin, .dat and .lst files from the content browser")) + scanner.refresh(); + ImGui::Text("Automatic State:"); + OptionCheckbox("Load", config::AutoLoadState, + "Load the last saved state of the game when starting"); + ImGui::SameLine(); + OptionCheckbox("Save", config::AutoSaveState, + "Save the state of the game when stopping"); + OptionCheckbox("Naomi Free Play", config::ForceFreePlay, "Configure Naomi games in Free Play mode."); +#if USE_DISCORD + OptionCheckbox("Discord Presence", config::DiscordPresence, "Show which game you are playing on Discord"); +#endif +#ifdef USE_RACHIEVEMENTS + OptionCheckbox("Enable RetroAchievements", config::EnableAchievements, "Track your game achievements using RetroAchievements.org"); + { + DisabledScope _(!config::EnableAchievements); + ImGui::Indent(); + OptionCheckbox("Hardcore Mode", config::AchievementsHardcoreMode, + "Enable RetroAchievements hardcore mode. Using cheats and loading a state are not allowed in this mode."); + ImGui::InputText("Username", &config::AchievementsUserName.get(), + achievements::isLoggedOn() ? ImGuiInputTextFlags_ReadOnly : ImGuiInputTextFlags_None, nullptr, nullptr); + if (config::EnableAchievements) + { + static std::future futureLogin; + achievements::init(); + if (achievements::isLoggedOn()) + { + ImGui::Text("Authentication successful"); + if (futureLogin.valid()) + futureLogin.get(); + if (ImGui::Button("Logout", ScaledVec2(100, 0))) + achievements::logout(); + } + else + { + static char password[256]; + ImGui::InputText("Password", password, sizeof(password), ImGuiInputTextFlags_Password, nullptr, nullptr); + if (futureLogin.valid()) + { + if (futureLogin.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) { + ImGui::Text("Authenticating..."); + } + else + { + try { + futureLogin.get(); + } catch (const FlycastException& e) { + gui_error(e.what()); + } + } + } + if (ImGui::Button("Login", ScaledVec2(100, 0)) && !futureLogin.valid()) + { + futureLogin = achievements::login(config::AchievementsUserName.get().c_str(), password); + memset(password, 0, sizeof(password)); + } + } + } + ImGui::Unindent(); + } +#endif +} + +static void gui_settings_controls(bool& maple_devices_changed) +{ + header("Physical Devices"); + { + if (ImGui::BeginTable("physicalDevices", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) + { + ImGui::TableSetupColumn("System", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("Port", ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); + + const float portComboWidth = calcComboWidth("None"); + const ImVec4 gray{ 0.5f, 0.5f, 0.5f, 1.f }; + + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::TextColored(gray, "System"); + + ImGui::TableSetColumnIndex(1); + ImGui::TextColored(gray, "Name"); + + ImGui::TableSetColumnIndex(2); + ImGui::TextColored(gray, "Port"); + + for (int i = 0; i < GamepadDevice::GetGamepadCount(); i++) + { + std::shared_ptr gamepad = GamepadDevice::GetGamepad(i); + if (!gamepad) + continue; + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("%s", gamepad->api_name().c_str()); + + ImGui::TableSetColumnIndex(1); + ImGui::Text("%s", gamepad->name().c_str()); + + ImGui::TableSetColumnIndex(2); + char port_name[32]; + sprintf(port_name, "##mapleport%d", i); + ImguiID _(port_name); + ImGui::SetNextItemWidth(portComboWidth); + if (ImGui::BeginCombo(port_name, maple_ports[gamepad->maple_port() + 1])) + { + for (int j = -1; j < (int)std::size(maple_ports) - 1; j++) + { + bool is_selected = gamepad->maple_port() == j; + if (ImGui::Selectable(maple_ports[j + 1], &is_selected)) + gamepad->set_maple_port(j); + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + + ImGui::EndCombo(); + } + + ImGui::TableSetColumnIndex(3); + ImGui::SameLine(0, uiScaled(8)); + if (gamepad->remappable() && ImGui::Button("Map")) + { + gamepad_port = 0; + ImGui::OpenPopup("Controller Mapping"); + } + + controller_mapping_popup(gamepad); + +#ifdef __ANDROID__ + if (gamepad->is_virtual_gamepad()) + { + if (ImGui::Button("Edit Layout")) + { + vjoy_start_editing(); + gui_setState(GuiState::VJoyEdit); + } + } +#endif + if (gamepad->is_rumble_enabled() || gamepad->has_analog_stick() +#ifdef __ANDROID__ + || gamepad->is_virtual_gamepad() +#endif + ) + { + ImGui::SameLine(0, uiScaled(16)); + if (ImGui::Button("Settings")) + ImGui::OpenPopup("Gamepad Settings"); + gamepadSettingsPopup(gamepad); + } + } + ImGui::EndTable(); + } + } + + ImGui::Spacing(); + OptionSlider("Mouse sensitivity", config::MouseSensitivity, 1, 500); +#if defined(_WIN32) && !defined(TARGET_UWP) + OptionCheckbox("Use Raw Input", config::UseRawInput, "Supports multiple pointing devices (mice, light guns) and keyboards"); +#endif + + ImGui::Spacing(); + header("Dreamcast Devices"); + { + bool is_there_any_xhair = false; + if (ImGui::BeginTable("dreamcastDevices", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings, + ImVec2(0, 0), uiScaled(8))) + { + const float mainComboWidth = calcComboWidth(maple_device_types[11]); // densha de go! controller + const float expComboWidth = calcComboWidth(maple_expansion_device_types[2]); // vibration pack + + for (int bus = 0; bus < MAPLE_PORTS; bus++) + { + ImGui::TableNextRow(); + ImGui::TableSetColumnIndex(0); + ImGui::Text("Port %c", bus + 'A'); + + ImGui::TableSetColumnIndex(1); + char device_name[32]; + sprintf(device_name, "##device%d", bus); + float w = ImGui::CalcItemWidth() / 3; + ImGui::PushItemWidth(w); + ImGui::SetNextItemWidth(mainComboWidth); + if (ImGui::BeginCombo(device_name, maple_device_name(config::MapleMainDevices[bus]), ImGuiComboFlags_None)) + { + for (int i = 0; i < IM_ARRAYSIZE(maple_device_types); i++) + { + bool is_selected = config::MapleMainDevices[bus] == maple_device_type_from_index(i); + if (ImGui::Selectable(maple_device_types[i], &is_selected)) + { + config::MapleMainDevices[bus] = maple_device_type_from_index(i); + maple_devices_changed = true; + } + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + int port_count = 0; + switch (config::MapleMainDevices[bus]) { + case MDT_SegaController: + port_count = 2; + break; + case MDT_LightGun: + case MDT_TwinStick: + case MDT_AsciiStick: + case MDT_RacingController: + port_count = 1; + break; + default: break; + } + for (int port = 0; port < port_count; port++) + { + ImGui::TableSetColumnIndex(2 + port); + sprintf(device_name, "##device%d.%d", bus, port + 1); + ImguiID _(device_name); + ImGui::SetNextItemWidth(expComboWidth); + if (ImGui::BeginCombo(device_name, maple_expansion_device_name(config::MapleExpansionDevices[bus][port]), ImGuiComboFlags_None)) + { + for (int i = 0; i < IM_ARRAYSIZE(maple_expansion_device_types); i++) + { + bool is_selected = config::MapleExpansionDevices[bus][port] == maple_expansion_device_type_from_index(i); + if (ImGui::Selectable(maple_expansion_device_types[i], &is_selected)) + { + config::MapleExpansionDevices[bus][port] = maple_expansion_device_type_from_index(i); + maple_devices_changed = true; + } + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + if (config::MapleMainDevices[bus] == MDT_LightGun) + { + ImGui::TableSetColumnIndex(3); + sprintf(device_name, "##device%d.xhair", bus); + ImguiID _(device_name); + u32 color = config::CrosshairColor[bus]; + float xhairColor[4] { + (color & 0xff) / 255.f, + ((color >> 8) & 0xff) / 255.f, + ((color >> 16) & 0xff) / 255.f, + ((color >> 24) & 0xff) / 255.f + }; + bool colorChanged = ImGui::ColorEdit4("Crosshair color", xhairColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf + | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoLabel); + ImGui::SameLine(); + bool enabled = color != 0; + if (ImGui::Checkbox("Crosshair", &enabled) || colorChanged) + { + if (enabled) + { + config::CrosshairColor[bus] = (u8)(std::round(xhairColor[0] * 255.f)) + | ((u8)(std::round(xhairColor[1] * 255.f)) << 8) + | ((u8)(std::round(xhairColor[2] * 255.f)) << 16) + | ((u8)(std::round(xhairColor[3] * 255.f)) << 24); + if (config::CrosshairColor[bus] == 0) + config::CrosshairColor[bus] = 0xC0FFFFFF; + } + else + { + config::CrosshairColor[bus] = 0; + } + } + is_there_any_xhair |= enabled; + } + ImGui::PopItemWidth(); + } + ImGui::EndTable(); + } + { + DisabledScope scope(!is_there_any_xhair); + OptionSlider("Crosshair Size", config::CrosshairSize, 10, 100); + } + OptionCheckbox("Per Game VMU A1", config::PerGameVmu, "When enabled, each game has its own VMU on port 1 of controller A."); + } +} + +static void gui_settings_video() +{ + int renderApi; + bool perPixel; + switch (config::RendererType) + { + default: + case RenderType::OpenGL: + renderApi = 0; + perPixel = false; + break; + case RenderType::OpenGL_OIT: + renderApi = 0; + perPixel = true; + break; + case RenderType::Vulkan: + renderApi = 1; + perPixel = false; + break; + case RenderType::Vulkan_OIT: + renderApi = 1; + perPixel = true; + break; + case RenderType::DirectX9: + renderApi = 2; + perPixel = false; + break; + case RenderType::DirectX11: + renderApi = 3; + perPixel = false; + break; + case RenderType::DirectX11_OIT: + renderApi = 3; + perPixel = true; + break; + } + + constexpr int apiCount = 0 + #ifdef USE_VULKAN + + 1 + #endif + #ifdef USE_DX9 + + 1 + #endif + #ifdef USE_OPENGL + + 1 + #endif + #ifdef USE_DX11 + + 1 + #endif + ; + + float innerSpacing = ImGui::GetStyle().ItemInnerSpacing.x; + if (apiCount > 1) + { + header("Graphics API"); + { + ImGui::Columns(apiCount, "renderApi", false); +#ifdef USE_OPENGL + ImGui::RadioButton("OpenGL", &renderApi, 0); + ImGui::NextColumn(); +#endif +#ifdef USE_VULKAN +#ifdef __APPLE__ + ImGui::RadioButton("Vulkan (Metal)", &renderApi, 1); + ImGui::SameLine(0, innerSpacing); + ShowHelpMarker("MoltenVK: An implementation of Vulkan that runs on Apple's Metal graphics framework"); +#else + ImGui::RadioButton("Vulkan", &renderApi, 1); +#endif // __APPLE__ + ImGui::NextColumn(); +#endif +#ifdef USE_DX9 + ImGui::RadioButton("DirectX 9", &renderApi, 2); + ImGui::NextColumn(); +#endif +#ifdef USE_DX11 + ImGui::RadioButton("DirectX 11", &renderApi, 3); + ImGui::NextColumn(); +#endif + ImGui::Columns(1, nullptr, false); + } + } + header("Transparent Sorting"); + { + const bool has_per_pixel = GraphicsContext::Instance()->hasPerPixel(); + int renderer = perPixel ? 2 : config::PerStripSorting ? 1 : 0; + ImGui::Columns(has_per_pixel ? 3 : 2, "renderers", false); + ImGui::RadioButton("Per Triangle", &renderer, 0); + ImGui::SameLine(); + ShowHelpMarker("Sort transparent polygons per triangle. Fast but may produce graphical glitches"); + ImGui::NextColumn(); + ImGui::RadioButton("Per Strip", &renderer, 1); + ImGui::SameLine(); + ShowHelpMarker("Sort transparent polygons per strip. Faster but may produce graphical glitches"); + if (has_per_pixel) + { + ImGui::NextColumn(); + ImGui::RadioButton("Per Pixel", &renderer, 2); + ImGui::SameLine(); + ShowHelpMarker("Sort transparent polygons per pixel. Slower but accurate"); + } + ImGui::Columns(1, NULL, false); + switch (renderer) + { + case 0: + perPixel = false; + config::PerStripSorting.set(false); + break; + case 1: + perPixel = false; + config::PerStripSorting.set(true); + break; + case 2: + perPixel = true; + break; + } + } + ImGui::Spacing(); + + header("Rendering Options"); + { + const std::array scalings{ 0.5f, 1.f, 1.5f, 2.f, 2.5f, 3.f, 4.f, 4.5f, 5.f, 6.f, 7.f, 8.f, 9.f }; + const std::array scalingsText{ "Half", "Native", "x1.5", "x2", "x2.5", "x3", "x4", "x4.5", "x5", "x6", "x7", "x8", "x9" }; + std::array vres; + std::array resLabels; + u32 selected = 0; + for (u32 i = 0; i < scalings.size(); i++) + { + vres[i] = scalings[i] * 480; + if (vres[i] == config::RenderResolution) + selected = i; + if (!config::Widescreen) + resLabels[i] = std::to_string((int)(scalings[i] * 640)) + "x" + std::to_string((int)(scalings[i] * 480)); + else + resLabels[i] = std::to_string((int)(scalings[i] * 480 * 16 / 9)) + "x" + std::to_string((int)(scalings[i] * 480)); + resLabels[i] += " (" + scalingsText[i] + ")"; + } + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); + if (ImGui::BeginCombo("##Resolution", resLabels[selected].c_str(), ImGuiComboFlags_NoArrowButton)) + { + for (u32 i = 0; i < scalings.size(); i++) + { + bool is_selected = vres[i] == config::RenderResolution; + if (ImGui::Selectable(resLabels[i].c_str(), is_selected)) + config::RenderResolution = vres[i]; + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(0, innerSpacing); + + if (ImGui::ArrowButton("##Decrease Res", ImGuiDir_Left)) + { + if (selected > 0) + config::RenderResolution = vres[selected - 1]; + } + ImGui::SameLine(0, innerSpacing); + if (ImGui::ArrowButton("##Increase Res", ImGuiDir_Right)) + { + if (selected < vres.size() - 1) + config::RenderResolution = vres[selected + 1]; + } + ImGui::SameLine(0, innerSpacing); + + ImGui::Text("Internal Resolution"); + ImGui::SameLine(); + ShowHelpMarker("Internal render resolution. Higher is better, but more demanding on the GPU. Values higher than your display resolution (but no more than double your display resolution) can be used for supersampling, which provides high-quality antialiasing without reducing sharpness."); + +#ifndef TARGET_IPHONE + OptionCheckbox("VSync", config::VSync, "Synchronizes the frame rate with the screen refresh rate. Recommended"); + if (isVulkan(config::RendererType)) + { + ImGui::Indent(); + { + DisabledScope scope(!config::VSync); + + OptionCheckbox("Duplicate frames", config::DupeFrames, "Duplicate frames on high refresh rate monitors (120 Hz and higher)"); + } + ImGui::Unindent(); + } +#endif + OptionCheckbox("Show VMU In-game", config::FloatVMUs, "Show the VMU LCD screens while in-game"); + OptionCheckbox("Full Framebuffer Emulation", config::EmulateFramebuffer, + "Fully accurate VRAM framebuffer emulation. Helps games that directly access the framebuffer for special effects. " + "Very slow and incompatible with upscaling and wide screen."); + OptionCheckbox("Load Custom Textures", config::CustomTextures, + "Load custom/high-res textures from data/textures/"); + } + ImGui::Spacing(); + header("Aspect Ratio"); + { + OptionCheckbox("Widescreen", config::Widescreen, + "Draw geometry outside of the normal 4:3 aspect ratio. May produce graphical glitches in the revealed areas.\nAspect Fit and shows the full 16:9 content."); + { + DisabledScope scope(!config::Widescreen); + + ImGui::Indent(); + OptionCheckbox("Super Widescreen", config::SuperWidescreen, + "Use the full width of the screen or window when its aspect ratio is greater than 16:9.\nAspect Fill and remove black bars."); + ImGui::Unindent(); + } + OptionCheckbox("Widescreen Game Cheats", config::WidescreenGameHacks, + "Modify the game so that it displays in 16:9 anamorphic format and use horizontal screen stretching. Only some games are supported."); + OptionSlider("Horizontal Stretching", config::ScreenStretching, 100, 250, + "Stretch the screen horizontally", "%d%%"); + OptionCheckbox("Rotate Screen 90°", config::Rotate90, "Rotate the screen 90° counterclockwise"); + } + if (perPixel) + { + ImGui::Spacing(); + header("Per Pixel Settings"); + + const std::array bufSizes{ 512_MB, 1_GB, 2_GB, 4_GB }; + const std::array bufSizesText{ "512 MB", "1 GB", "2 GB", "4 GB" }; + ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); + u32 selected = 0; + for (; selected < bufSizes.size(); selected++) + if (bufSizes[selected] == config::PixelBufferSize) + break; + if (selected == bufSizes.size()) + selected = 0; + if (ImGui::BeginCombo("##PixelBuffer", bufSizesText[selected].c_str(), ImGuiComboFlags_NoArrowButton)) + { + for (u32 i = 0; i < bufSizes.size(); i++) + { + bool is_selected = i == selected; + if (ImGui::Selectable(bufSizesText[i].c_str(), is_selected)) + config::PixelBufferSize = bufSizes[i]; + if (is_selected) { + ImGui::SetItemDefaultFocus(); + selected = i; + } + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(0, innerSpacing); + + if (ImGui::ArrowButton("##Decrease BufSize", ImGuiDir_Left)) + { + if (selected > 0) + config::PixelBufferSize = bufSizes[selected - 1]; + } + ImGui::SameLine(0, innerSpacing); + if (ImGui::ArrowButton("##Increase BufSize", ImGuiDir_Right)) + { + if (selected < bufSizes.size() - 1) + config::PixelBufferSize = bufSizes[selected + 1]; + } + ImGui::SameLine(0, innerSpacing); + + ImGui::Text("Pixel Buffer Size"); + ImGui::SameLine(); + ShowHelpMarker("The size of the pixel buffer. May need to be increased when upscaling by a large factor."); + + OptionSlider("Maximum Layers", config::PerPixelLayers, 8, 128, + "Maximum number of transparent layers. May need to be increased for some complex scenes. Decreasing it may improve performance."); + } + ImGui::Spacing(); + header("Performance"); + { + ImGui::Text("Automatic Frame Skipping:"); + ImGui::Columns(3, "autoskip", false); + OptionRadioButton("Disabled", config::AutoSkipFrame, 0, "No frame skipping"); + ImGui::NextColumn(); + OptionRadioButton("Normal", config::AutoSkipFrame, 1, "Skip a frame when the GPU and CPU are both running slow"); + ImGui::NextColumn(); + OptionRadioButton("Maximum", config::AutoSkipFrame, 2, "Skip a frame when the GPU is running slow"); + ImGui::Columns(1, nullptr, false); + + OptionArrowButtons("Frame Skipping", config::SkipFrame, 0, 6, + "Number of frames to skip between two actually rendered frames"); + OptionCheckbox("Shadows", config::ModifierVolumes, + "Enable modifier volumes, usually used for shadows"); + OptionCheckbox("Fog", config::Fog, "Enable fog effects"); + } + ImGui::Spacing(); + header("Advanced"); + { + OptionCheckbox("Delay Frame Swapping", config::DelayFrameSwapping, + "Useful to avoid flashing screen or glitchy videos. Not recommended on slow platforms"); + OptionCheckbox("Fix Upscale Bleeding Edge", config::FixUpscaleBleedingEdge, + "Helps with texture bleeding case when upscaling. Disabling it can help if pixels are warping when upscaling in 2D games (MVC2, CVS, KOF, etc.)"); + OptionCheckbox("Native Depth Interpolation", config::NativeDepthInterpolation, + "Helps with texture corruption and depth issues on AMD GPUs. Can also help Intel GPUs in some cases."); + OptionCheckbox("Copy Rendered Textures to VRAM", config::RenderToTextureBuffer, + "Copy rendered-to textures back to VRAM. Slower but accurate"); + const std::array aniso{ 1, 2, 4, 8, 16 }; + const std::array anisoText{ "Disabled", "2x", "4x", "8x", "16x" }; + u32 afSelected = 0; + for (u32 i = 0; i < aniso.size(); i++) + { + if (aniso[i] == config::AnisotropicFiltering) + afSelected = i; + } + + ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); + if (ImGui::BeginCombo("##Anisotropic Filtering", anisoText[afSelected].c_str(), ImGuiComboFlags_NoArrowButton)) + { + for (u32 i = 0; i < aniso.size(); i++) + { + bool is_selected = aniso[i] == config::AnisotropicFiltering; + if (ImGui::Selectable(anisoText[i].c_str(), is_selected)) + config::AnisotropicFiltering = aniso[i]; + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + ImGui::PopItemWidth(); + ImGui::SameLine(0, innerSpacing); + + if (ImGui::ArrowButton("##Decrease Anisotropic Filtering", ImGuiDir_Left)) + { + if (afSelected > 0) + config::AnisotropicFiltering = aniso[afSelected - 1]; + } + ImGui::SameLine(0, innerSpacing); + if (ImGui::ArrowButton("##Increase Anisotropic Filtering", ImGuiDir_Right)) + { + if (afSelected < aniso.size() - 1) + config::AnisotropicFiltering = aniso[afSelected + 1]; + } + ImGui::SameLine(0, innerSpacing); + + ImGui::Text("Anisotropic Filtering"); + ImGui::SameLine(); + ShowHelpMarker("Higher values make textures viewed at oblique angles look sharper, but are more demanding on the GPU. This option only has a visible impact on mipmapped textures."); + + ImGui::Text("Texture Filtering:"); + ImGui::Columns(3, "textureFiltering", false); + OptionRadioButton("Default", config::TextureFiltering, 0, "Use the game's default texture filtering"); + ImGui::NextColumn(); + OptionRadioButton("Force Nearest-Neighbor", config::TextureFiltering, 1, "Force nearest-neighbor filtering for all textures. Crisper appearance, but may cause various rendering issues. This option usually does not affect performance."); + ImGui::NextColumn(); + OptionRadioButton("Force Linear", config::TextureFiltering, 2, "Force linear filtering for all textures. Smoother appearance, but may cause various rendering issues. This option usually does not affect performance."); + ImGui::Columns(1, nullptr, false); + + OptionCheckbox("Show FPS Counter", config::ShowFPS, "Show on-screen frame/sec counter"); + } + ImGui::Spacing(); + header("Texture Upscaling"); + { +#ifdef _OPENMP + OptionArrowButtons("Texture Upscaling", config::TextureUpscale, 1, 8, + "Upscale textures with the xBRZ algorithm. Only on fast platforms and for certain 2D games", "x%d"); + OptionSlider("Texture Max Size", config::MaxFilteredTextureSize, 8, 1024, + "Textures larger than this dimension squared will not be upscaled"); + OptionArrowButtons("Max Threads", config::MaxThreads, 1, 8, + "Maximum number of threads to use for texture upscaling. Recommended: number of physical cores minus one"); +#endif + } +#ifdef VIDEO_ROUTING +#ifdef __APPLE__ + header("Video Routing (Syphon)"); +#elif defined(_WIN32) + ((renderApi == 0) || (renderApi == 3)) ? header("Video Routing (Spout)") : header("Video Routing (Only available with OpenGL or DirectX 11)"); +#endif + { +#ifdef _WIN32 + DisabledScope scope(!((renderApi == 0) || (renderApi == 3))); +#endif + OptionCheckbox("Send video content to another program", config::VideoRouting, + "e.g. Route GPU texture to OBS Studio directly instead of using CPU intensive Display/Window Capture"); + + { + DisabledScope scope(!config::VideoRouting); + OptionCheckbox("Scale down before sending", config::VideoRoutingScale, "Could increase performance when sharing a smaller texture, YMMV"); + { + DisabledScope scope(!config::VideoRoutingScale); + static int vres = config::VideoRoutingVRes; + if (ImGui::InputInt("Output vertical resolution", &vres)) + { + config::VideoRoutingVRes = vres; + } + } + ImGui::Text("Output texture size: %d x %d", config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width, config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height); + } + } +#endif + + switch (renderApi) + { + case 0: + config::RendererType = perPixel ? RenderType::OpenGL_OIT : RenderType::OpenGL; + break; + case 1: + config::RendererType = perPixel ? RenderType::Vulkan_OIT : RenderType::Vulkan; + break; + case 2: + config::RendererType = RenderType::DirectX9; + break; + case 3: + config::RendererType = perPixel ? RenderType::DirectX11_OIT : RenderType::DirectX11; + break; + } +} + +static void gui_settings_audio() +{ + OptionCheckbox("Enable DSP", config::DSPEnabled, + "Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms"); + OptionCheckbox("Enable VMU Sounds", config::VmuSound, "Play VMU beeps when enabled."); + + if (OptionSlider("Volume Level", config::AudioVolume, 0, 100, "Adjust the emulator's audio level", "%d%%")) + { + config::AudioVolume.calcDbPower(); + }; +#ifdef __ANDROID__ + if (config::AudioBackend.get() == "auto" || config::AudioBackend.get() == "android") + OptionCheckbox("Automatic Latency", config::AutoLatency, + "Automatically set audio latency. Recommended"); +#endif + if (!config::AutoLatency + || (config::AudioBackend.get() != "auto" && config::AudioBackend.get() != "android")) + { + int latency = (int)roundf(config::AudioBufferSize * 1000.f / 44100.f); + ImGui::SliderInt("Latency", &latency, 12, 512, "%d ms"); + config::AudioBufferSize = (int)roundf(latency * 44100.f / 1000.f); + ImGui::SameLine(); + ShowHelpMarker("Sets the maximum audio latency. Not supported by all audio drivers."); + } + + AudioBackend *backend = nullptr; + std::string backend_name = config::AudioBackend; + if (backend_name != "auto") + { + backend = AudioBackend::getBackend(config::AudioBackend); + if (backend != nullptr) + backend_name = backend->slug; + } + + AudioBackend *current_backend = backend; + if (ImGui::BeginCombo("Audio Driver", backend_name.c_str(), ImGuiComboFlags_None)) + { + bool is_selected = (config::AudioBackend.get() == "auto"); + if (ImGui::Selectable("auto - Automatic driver selection", &is_selected)) + config::AudioBackend.set("auto"); + + for (u32 i = 0; i < AudioBackend::getCount(); i++) + { + backend = AudioBackend::getBackend(i); + is_selected = (config::AudioBackend.get() == backend->slug); + + if (is_selected) + current_backend = backend; + + if (ImGui::Selectable((backend->slug + " - " + backend->name).c_str(), &is_selected)) + config::AudioBackend.set(backend->slug); + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ShowHelpMarker("The audio driver to use"); + + if (current_backend != nullptr) + { + // get backend specific options + int option_count; + const AudioBackend::Option *options = current_backend->getOptions(&option_count); + + for (int o = 0; o < option_count; o++) + { + std::string value = cfgLoadStr(current_backend->slug, options->name, ""); + + if (options->type == AudioBackend::Option::integer) + { + int val = stoi(value); + if (ImGui::SliderInt(options->caption.c_str(), &val, options->minValue, options->maxValue)) + { + std::string s = std::to_string(val); + cfgSaveStr(current_backend->slug, options->name, s); + } + } + else if (options->type == AudioBackend::Option::checkbox) + { + bool check = value == "1"; + if (ImGui::Checkbox(options->caption.c_str(), &check)) + cfgSaveStr(current_backend->slug, options->name, + check ? "1" : "0"); + } + else if (options->type == AudioBackend::Option::list) + { + if (ImGui::BeginCombo(options->caption.c_str(), value.c_str(), ImGuiComboFlags_None)) + { + bool is_selected = false; + for (const auto& cur : options->values) + { + is_selected = value == cur; + if (ImGui::Selectable(cur.c_str(), &is_selected)) + cfgSaveStr(current_backend->slug, options->name, cur); + + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + } + else { + WARN_LOG(RENDERER, "Unknown option"); + } + + options++; + } + } +} + +static void gui_settings_network() +{ + ImGuiStyle& style = ImGui::GetStyle(); + header("Network Type"); + { + DisabledScope scope(game_started); + + int netType = 0; + if (config::GGPOEnable) + netType = 1; + else if (config::NetworkEnable) + netType = 2; + else if (config::BattleCableEnable) + netType = 3; + ImGui::Columns(4, "networkType", false); + ImGui::RadioButton("Disabled", &netType, 0); + ImGui::NextColumn(); + ImGui::RadioButton("GGPO", &netType, 1); + ImGui::SameLine(0, style.ItemInnerSpacing.x); + ShowHelpMarker("Enable networking using GGPO"); + ImGui::NextColumn(); + ImGui::RadioButton("Naomi", &netType, 2); + ImGui::SameLine(0, style.ItemInnerSpacing.x); + ShowHelpMarker("Enable networking for supported Naomi and Atomiswave games"); + ImGui::NextColumn(); + ImGui::RadioButton("Battle Cable", &netType, 3); + ImGui::SameLine(0, style.ItemInnerSpacing.x); + ShowHelpMarker("Emulate the Taisen (Battle) null modem cable for games that support it"); + ImGui::Columns(1, nullptr, false); + + config::GGPOEnable = false; + config::NetworkEnable = false; + config::BattleCableEnable = false; + switch (netType) { + case 1: + config::GGPOEnable = true; + break; + case 2: + config::NetworkEnable = true; + break; + case 3: + config::BattleCableEnable = true; + break; + } + } + if (config::GGPOEnable || config::NetworkEnable || config::BattleCableEnable) { + ImGui::Spacing(); + header("Configuration"); + } + { + if (config::GGPOEnable) + { + config::NetworkEnable = false; + OptionCheckbox("Play as Player 1", config::ActAsServer, + "Deselect to play as player 2"); + ImGui::InputText("Peer", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("Your peer IP address and optional port"); + OptionSlider("Frame Delay", config::GGPODelay, 0, 20, + "Sets Frame Delay, advisable for sessions with ping >100 ms"); + + ImGui::Text("Left Thumbstick:"); + OptionRadioButton("Disabled", config::GGPOAnalogAxes, 0, "Left thumbstick not used"); + ImGui::SameLine(); + OptionRadioButton("Horizontal", config::GGPOAnalogAxes, 1, "Use the left thumbstick horizontal axis only"); + ImGui::SameLine(); + OptionRadioButton("Full", config::GGPOAnalogAxes, 2, "Use the left thumbstick horizontal and vertical axes"); + + OptionCheckbox("Enable Chat", config::GGPOChat, "Open the chat window when a chat message is received"); + if (config::GGPOChat) + { + OptionCheckbox("Enable Chat Window Timeout", config::GGPOChatTimeoutToggle, "Automatically close chat window after 20 seconds"); + if (config::GGPOChatTimeoutToggle) + { + char chatTimeout[256]; + sprintf(chatTimeout, "%d", (int)config::GGPOChatTimeout); + ImGui::InputText("Chat Window Timeout (seconds)", chatTimeout, sizeof(chatTimeout), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("Sets duration that chat window stays open after new message is received."); + config::GGPOChatTimeout.set(atoi(chatTimeout)); + } + } + OptionCheckbox("Network Statistics", config::NetworkStats, + "Display network statistics on screen"); + } + else if (config::NetworkEnable) + { + OptionCheckbox("Act as Server", config::ActAsServer, + "Create a local server for Naomi network games"); + if (!config::ActAsServer) + { + ImGui::InputText("Server", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("The server to connect to. Leave blank to find a server automatically on the default port"); + } + char localPort[256]; + sprintf(localPort, "%d", (int)config::LocalPort); + ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("The local UDP port to use"); + config::LocalPort.set(atoi(localPort)); + } + else if (config::BattleCableEnable) + { + ImGui::InputText("Peer", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("The peer to connect to. Leave blank to find a player automatically on the default port"); + char localPort[256]; + sprintf(localPort, "%d", (int)config::LocalPort); + ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("The local UDP port to use"); + config::LocalPort.set(atoi(localPort)); + } + } + ImGui::Spacing(); + header("Network Options"); + { + OptionCheckbox("Enable UPnP", config::EnableUPnP, "Automatically configure your network router for netplay"); + OptionCheckbox("Broadcast Digital Outputs", config::NetworkOutput, "Broadcast digital outputs and force-feedback state on TCP port 8000. " + "Compatible with the \"-output network\" MAME option. Arcade games only."); + { + DisabledScope scope(game_started); + + OptionCheckbox("Broadband Adapter Emulation", config::EmulateBBA, + "Emulate the Ethernet Broadband Adapter (BBA) instead of the Modem"); + } + } +#ifdef NAOMI_MULTIBOARD + ImGui::Spacing(); + header("Multiboard Screens"); + { + //OptionRadioButton("Disabled", config::MultiboardSlaves, 0, "Multiboard disabled (when optional)"); + OptionRadioButton("1 (Twin)", config::MultiboardSlaves, 1, "One screen configuration (F355 Twin)"); + ImGui::SameLine(); + OptionRadioButton("3 (Deluxe)", config::MultiboardSlaves, 2, "Three screens configuration"); + } +#endif +} + +static void gui_settings_advanced() +{ + header("CPU Mode"); + { + ImGui::Columns(2, "cpu_modes", false); + OptionRadioButton("Dynarec", config::DynarecEnabled, true, + "Use the dynamic recompiler. Recommended in most cases"); + ImGui::NextColumn(); + OptionRadioButton("Interpreter", config::DynarecEnabled, false, + "Use the interpreter. Very slow but may help in case of a dynarec problem"); + ImGui::Columns(1, NULL, false); + + OptionSlider("SH4 Clock", config::Sh4Clock, 100, 300, + "Over/Underclock the main SH4 CPU. Default is 200 MHz. Other values may crash, freeze or trigger unexpected nuclear reactions.", + "%d MHz"); + } + ImGui::Spacing(); + header("Other"); + { + OptionCheckbox("HLE BIOS", config::UseReios, "Force high-level BIOS emulation"); + OptionCheckbox("Multi-threaded emulation", config::ThreadedRendering, + "Run the emulated CPU and GPU on different threads"); +#ifndef __ANDROID + OptionCheckbox("Serial Console", config::SerialConsole, + "Dump the Dreamcast serial console to stdout"); +#endif + { + DisabledScope scope(game_started); + OptionCheckbox("Dreamcast 32MB RAM Mod", config::RamMod32MB, + "Enables 32MB RAM Mod for Dreamcast. May affect compatibility"); + } + OptionCheckbox("Dump Textures", config::DumpTextures, + "Dump all textures into data/texdump/"); + + bool logToFile = cfgLoadBool("log", "LogToFile", false); + bool newLogToFile = logToFile; + ImGui::Checkbox("Log to File", &newLogToFile); + if (logToFile != newLogToFile) + { + cfgSaveBool("log", "LogToFile", newLogToFile); + LogManager::Shutdown(); + LogManager::Init(); + } + ImGui::SameLine(); + ShowHelpMarker("Log debug information to flycast.log"); +#ifdef SENTRY_UPLOAD + OptionCheckbox("Automatically Report Crashes", config::UploadCrashLogs, + "Automatically upload crash reports to sentry.io to help in troubleshooting. No personal information is included."); +#endif + } + +#ifdef USE_LUA + header("Lua Scripting"); + { + ImGui::InputText("Lua Filename", &config::LuaFileName.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); + ImGui::SameLine(); + ShowHelpMarker("Specify lua filename to use. Should be located in Flycast config directory. Defaults to flycast.lua when empty."); + } +#endif +} + +static void gui_settings_about() +{ + header("Flycast"); + { + ImGui::Text("Version: %s", GIT_VERSION); + ImGui::Text("Git Hash: %s", GIT_HASH); + ImGui::Text("Build Date: %s", BUILD_DATE); + } + ImGui::Spacing(); + header("Platform"); + { + ImGui::Text("CPU: %s", +#if HOST_CPU == CPU_X86 + "x86" +#elif HOST_CPU == CPU_ARM + "ARM" +#elif HOST_CPU == CPU_MIPS + "MIPS" +#elif HOST_CPU == CPU_X64 + "x86/64" +#elif HOST_CPU == CPU_GENERIC + "Generic" +#elif HOST_CPU == CPU_ARM64 + "ARM64" +#else + "Unknown" +#endif + ); + ImGui::Text("Operating System: %s", +#ifdef __ANDROID__ + "Android" +#elif defined(__unix__) + "Linux" +#elif defined(__APPLE__) +#ifdef TARGET_IPHONE + "iOS" +#else + "macOS" +#endif +#elif defined(TARGET_UWP) + "Windows Universal Platform" +#elif defined(_WIN32) + "Windows" +#elif defined(__SWITCH__) + "Switch" +#else + "Unknown" +#endif + ); +#ifdef TARGET_IPHONE + const char *getIosJitStatus(); + ImGui::Text("JIT Status: %s", getIosJitStatus()); +#endif + } + ImGui::Spacing(); + if (isOpenGL(config::RendererType)) + header("OpenGL"); + else if (isVulkan(config::RendererType)) + header("Vulkan"); + else if (isDirectX(config::RendererType)) + header("DirectX"); + ImGui::Text("Driver Name: %s", GraphicsContext::Instance()->getDriverName().c_str()); + ImGui::Text("Version: %s", GraphicsContext::Instance()->getDriverVersion().c_str()); + +#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 && USE_VULKAN + if (isVulkan(config::RendererType)) + { + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); + if (config::CustomGpuDriver) + { + std::string name, description, vendor, version; + if (getCustomGpuDriverInfo(name, description, vendor, version)) + { + ImGui::Text("Custom Driver:"); + ImGui::Indent(); + ImGui::Text("%s - %s", name.c_str(), description.c_str()); + ImGui::Text("%s - %s", vendor.c_str(), version.c_str()); + ImGui::Unindent(); + } + + if (ImGui::Button("Use Default Driver")) { + config::CustomGpuDriver = false; + ImGui::OpenPopup("Reset Vulkan"); + } + } + else if (ImGui::Button("Upload Custom Driver")) + ImGui::OpenPopup("Select custom GPU driver"); + + static bool driverDirty; + const auto& callback = [](bool cancelled, std::string selection) { + if (!cancelled) { + try { + uploadCustomGpuDriver(selection); + config::CustomGpuDriver = true; + driverDirty = true; + } catch (const FlycastException& e) { + gui_error(e.what()); + config::CustomGpuDriver = false; + } + } + return true; + }; + select_file_popup("Select custom GPU driver", callback, true, "zip"); + + if (driverDirty) { + ImGui::OpenPopup("Reset Vulkan"); + driverDirty = false; + } + + ImguiStyleVar _1(ImGuiStyleVar_WindowPadding, ScaledVec2(20, 20)); + if (ImGui::BeginPopupModal("Reset Vulkan", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) + { + ImGui::Text("Do you want to reset Vulkan to use new driver?"); + ImGui::NewLine(); + ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ImVec2(uiScaled(20), ImGui::GetStyle().ItemSpacing.y)); + ImguiStyleVar _1(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); + if (ImGui::Button("Yes")) + { + mainui_reinit(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No")) + ImGui::CloseCurrentPopup(); + ImGui::EndPopup(); + } + } +#endif +} + static void gui_display_settings() { static bool maple_devices_changed; @@ -1650,1287 +2915,51 @@ static void gui_display_settings() if (ImGui::BeginTabItem(ICON_FA_TOOLBOX " General")) { ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); - { - DisabledScope scope(settings.platform.isArcade()); - - const char *languages[] = { "Japanese", "English", "German", "French", "Spanish", "Italian", "Default" }; - OptionComboBox("Language", config::Language, languages, std::size(languages), - "The language as configured in the Dreamcast BIOS"); - - const char *broadcast[] = { "NTSC", "PAL", "PAL/M", "PAL/N", "Default" }; - OptionComboBox("Broadcast", config::Broadcast, broadcast, std::size(broadcast), - "TV broadcasting standard for non-VGA modes"); - } - - const char *consoleRegion[] = { "Japan", "USA", "Europe", "Default" }; - const char *arcadeRegion[] = { "Japan", "USA", "Export", "Korea" }; - const char **region = settings.platform.isArcade() ? arcadeRegion : consoleRegion; - OptionComboBox("Region", config::Region, region, std::size(consoleRegion), - "BIOS region"); - - const char *cable[] = { "VGA", "RGB Component", "TV Composite" }; - { - DisabledScope scope(config::Cable.isReadOnly() || settings.platform.isArcade()); - - const char *value = config::Cable == 0 ? cable[0] - : config::Cable > 0 && config::Cable <= (int)std::size(cable) ? cable[config::Cable - 1] - : "?"; - if (ImGui::BeginCombo("Cable", value, ImGuiComboFlags_None)) - { - for (int i = 0; i < IM_ARRAYSIZE(cable); i++) - { - bool is_selected = i == 0 ? config::Cable <= 1 : config::Cable - 1 == i; - if (ImGui::Selectable(cable[i], &is_selected)) - config::Cable = i == 0 ? 0 : i + 1; - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - ImGui::SameLine(); - ShowHelpMarker("Video connection type"); - } - -#if !defined(TARGET_IPHONE) - ImVec2 size; - size.x = 0.0f; - size.y = (ImGui::GetTextLineHeightWithSpacing() + ImGui::GetStyle().FramePadding.y * 2.f) - * (config::ContentPath.get().size() + 1) ;//+ ImGui::GetStyle().FramePadding.y * 2.f; - - if (BeginListBox("Content Location", size, ImGuiWindowFlags_NavFlattened)) - { - int to_delete = -1; - for (u32 i = 0; i < config::ContentPath.get().size(); i++) - { - ImguiID _(config::ContentPath.get()[i].c_str()); - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s", config::ContentPath.get()[i].c_str()); - ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("X").x - ImGui::GetStyle().FramePadding.x); - if (ImGui::Button("X")) - to_delete = i; - } - ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(24, 3)); -#ifdef __ANDROID__ - if (ImGui::Button("Add")) - { - hostfs::addStorage(true, false, [](bool cancelled, std::string selection) { - if (!cancelled) - addContentPath(selection); - }); - } -#else - if (ImGui::Button("Add")) - ImGui::OpenPopup("Select Directory"); - select_file_popup("Select Directory", [](bool cancelled, std::string selection) { - if (!cancelled) - addContentPath(selection); - return true; - }); -#endif - ImGui::SameLine(); - if (ImGui::Button("Rescan Content")) - scanner.refresh(); - scrollWhenDraggingOnVoid(); - - ImGui::EndListBox(); - if (to_delete >= 0) - { - scanner.stop(); - config::ContentPath.get().erase(config::ContentPath.get().begin() + to_delete); - scanner.refresh(); - } - } - ImGui::SameLine(); - ShowHelpMarker("The directories where your games are stored"); - - size.y = ImGui::GetTextLineHeightWithSpacing() * 1.25f + ImGui::GetStyle().FramePadding.y * 2.0f; - -#if defined(__linux__) && !defined(__ANDROID__) - if (BeginListBox("Data Directory", size, ImGuiWindowFlags_NavFlattened)) - { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s", get_writable_data_path("").c_str()); - ImGui::EndListBox(); - } - ImGui::SameLine(); - ShowHelpMarker("The directory containing BIOS files, as well as saved VMUs and states"); -#else - if (BeginListBox("Home Directory", size, ImGuiWindowFlags_NavFlattened)) - { - ImGui::AlignTextToFramePadding(); - ImGui::Text("%s", get_writable_config_path("").c_str()); -#ifdef __ANDROID__ - ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Change").x - ImGui::GetStyle().FramePadding.x); - if (ImGui::Button("Change")) - gui_setState(GuiState::Onboarding); -#endif -#ifdef TARGET_MAC - ImGui::SameLine(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize("Reveal in Finder").x - ImGui::GetStyle().FramePadding.x); - if (ImGui::Button("Reveal in Finder")) - { - char temp[512]; - sprintf(temp, "open \"%s\"", get_writable_config_path("").c_str()); - system(temp); - } -#endif - ImGui::EndListBox(); - } - ImGui::SameLine(); - ShowHelpMarker("The directory where Flycast saves configuration files and VMUs. BIOS files should be in a subfolder named \"data\""); -#endif // !linux -#endif // !TARGET_IPHONE - - OptionCheckbox("Box Art Game List", config::BoxartDisplayMode, - "Display game cover art in the game list."); - OptionCheckbox("Fetch Box Art", config::FetchBoxart, - "Fetch cover images from TheGamesDB.net."); - if (OptionSlider("UI Scaling", config::UIScaling, 50, 200, "Adjust the size of UI elements and fonts.", "%d%%")) - uiUserScaleUpdated = true; - if (uiUserScaleUpdated) - { - ImGui::SameLine(); - if (ImGui::Button("Apply")) { - mainui_reinit(); - uiUserScaleUpdated = false; - } - } - - if (OptionCheckbox("Hide Legacy Naomi Roms", config::HideLegacyNaomiRoms, - "Hide .bin, .dat and .lst files from the content browser")) - scanner.refresh(); - ImGui::Text("Automatic State:"); - OptionCheckbox("Load", config::AutoLoadState, - "Load the last saved state of the game when starting"); - ImGui::SameLine(); - OptionCheckbox("Save", config::AutoSaveState, - "Save the state of the game when stopping"); - OptionCheckbox("Naomi Free Play", config::ForceFreePlay, "Configure Naomi games in Free Play mode."); -#if USE_DISCORD - OptionCheckbox("Discord Presence", config::DiscordPresence, "Show which game you are playing on Discord"); -#endif -#ifdef USE_RACHIEVEMENTS - OptionCheckbox("Enable RetroAchievements", config::EnableAchievements, "Track your game achievements using RetroAchievements.org"); - { - DisabledScope _(!config::EnableAchievements); - ImGui::Indent(); - OptionCheckbox("Hardcore Mode", config::AchievementsHardcoreMode, - "Enable RetroAchievements hardcore mode. Using cheats and loading a state are not allowed in this mode."); - ImGui::InputText("Username", &config::AchievementsUserName.get(), - achievements::isLoggedOn() ? ImGuiInputTextFlags_ReadOnly : ImGuiInputTextFlags_None, nullptr, nullptr); - if (config::EnableAchievements) - { - static std::future futureLogin; - achievements::init(); - if (achievements::isLoggedOn()) - { - ImGui::Text("Authentication successful"); - if (futureLogin.valid()) - futureLogin.get(); - if (ImGui::Button("Logout", ScaledVec2(100, 0))) - achievements::logout(); - } - else - { - static char password[256]; - ImGui::InputText("Password", password, sizeof(password), ImGuiInputTextFlags_Password, nullptr, nullptr); - if (futureLogin.valid()) - { - if (futureLogin.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) { - ImGui::Text("Authenticating..."); - } - else - { - try { - futureLogin.get(); - } catch (const FlycastException& e) { - gui_error(e.what()); - } - } - } - if (ImGui::Button("Login", ScaledVec2(100, 0)) && !futureLogin.valid()) - { - futureLogin = achievements::login(config::AchievementsUserName.get().c_str(), password); - memset(password, 0, sizeof(password)); - } - } - } - ImGui::Unindent(); - } -#endif + gui_settings_general(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ICON_FA_GAMEPAD " Controls")) { ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); - header("Physical Devices"); - { - if (ImGui::BeginTable("physicalDevices", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) - { - ImGui::TableSetupColumn("System", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch); - ImGui::TableSetupColumn("Port", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("", ImGuiTableColumnFlags_WidthFixed); - - const float portComboWidth = calcComboWidth("None"); - const ImVec4 gray{ 0.5f, 0.5f, 0.5f, 1.f }; - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::TextColored(gray, "System"); - - ImGui::TableSetColumnIndex(1); - ImGui::TextColored(gray, "Name"); - - ImGui::TableSetColumnIndex(2); - ImGui::TextColored(gray, "Port"); - - for (int i = 0; i < GamepadDevice::GetGamepadCount(); i++) - { - std::shared_ptr gamepad = GamepadDevice::GetGamepad(i); - if (!gamepad) - continue; - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::Text("%s", gamepad->api_name().c_str()); - - ImGui::TableSetColumnIndex(1); - ImGui::Text("%s", gamepad->name().c_str()); - - ImGui::TableSetColumnIndex(2); - char port_name[32]; - sprintf(port_name, "##mapleport%d", i); - ImguiID _(port_name); - ImGui::SetNextItemWidth(portComboWidth); - if (ImGui::BeginCombo(port_name, maple_ports[gamepad->maple_port() + 1])) - { - for (int j = -1; j < (int)std::size(maple_ports) - 1; j++) - { - bool is_selected = gamepad->maple_port() == j; - if (ImGui::Selectable(maple_ports[j + 1], &is_selected)) - gamepad->set_maple_port(j); - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - - ImGui::EndCombo(); - } - - ImGui::TableSetColumnIndex(3); - ImGui::SameLine(0, uiScaled(8)); - if (gamepad->remappable() && ImGui::Button("Map")) - { - gamepad_port = 0; - ImGui::OpenPopup("Controller Mapping"); - } - - controller_mapping_popup(gamepad); - -#ifdef __ANDROID__ - if (gamepad->is_virtual_gamepad()) - { - if (ImGui::Button("Edit Layout")) - { - vjoy_start_editing(); - gui_setState(GuiState::VJoyEdit); - } - } -#endif - if (gamepad->is_rumble_enabled() || gamepad->has_analog_stick() -#ifdef __ANDROID__ - || gamepad->is_virtual_gamepad() -#endif - ) - { - ImGui::SameLine(0, uiScaled(16)); - if (ImGui::Button("Settings")) - ImGui::OpenPopup("Gamepad Settings"); - gamepadSettingsPopup(gamepad); - } - } - ImGui::EndTable(); - } - } - - ImGui::Spacing(); - OptionSlider("Mouse sensitivity", config::MouseSensitivity, 1, 500); -#if defined(_WIN32) && !defined(TARGET_UWP) - OptionCheckbox("Use Raw Input", config::UseRawInput, "Supports multiple pointing devices (mice, light guns) and keyboards"); -#endif - - ImGui::Spacing(); - header("Dreamcast Devices"); - { - bool is_there_any_xhair = false; - if (ImGui::BeginTable("dreamcastDevices", 4, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings, - ImVec2(0, 0), uiScaled(8))) - { - const float mainComboWidth = calcComboWidth(maple_device_types[11]); // densha de go! controller - const float expComboWidth = calcComboWidth(maple_expansion_device_types[2]); // vibration pack - - for (int bus = 0; bus < MAPLE_PORTS; bus++) - { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - ImGui::Text("Port %c", bus + 'A'); - - ImGui::TableSetColumnIndex(1); - char device_name[32]; - sprintf(device_name, "##device%d", bus); - float w = ImGui::CalcItemWidth() / 3; - ImGui::PushItemWidth(w); - ImGui::SetNextItemWidth(mainComboWidth); - if (ImGui::BeginCombo(device_name, maple_device_name(config::MapleMainDevices[bus]), ImGuiComboFlags_None)) - { - for (int i = 0; i < IM_ARRAYSIZE(maple_device_types); i++) - { - bool is_selected = config::MapleMainDevices[bus] == maple_device_type_from_index(i); - if (ImGui::Selectable(maple_device_types[i], &is_selected)) - { - config::MapleMainDevices[bus] = maple_device_type_from_index(i); - maple_devices_changed = true; - } - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - int port_count = 0; - switch (config::MapleMainDevices[bus]) { - case MDT_SegaController: - port_count = 2; - break; - case MDT_LightGun: - case MDT_TwinStick: - case MDT_AsciiStick: - case MDT_RacingController: - port_count = 1; - break; - default: break; - } - for (int port = 0; port < port_count; port++) - { - ImGui::TableSetColumnIndex(2 + port); - sprintf(device_name, "##device%d.%d", bus, port + 1); - ImguiID _(device_name); - ImGui::SetNextItemWidth(expComboWidth); - if (ImGui::BeginCombo(device_name, maple_expansion_device_name(config::MapleExpansionDevices[bus][port]), ImGuiComboFlags_None)) - { - for (int i = 0; i < IM_ARRAYSIZE(maple_expansion_device_types); i++) - { - bool is_selected = config::MapleExpansionDevices[bus][port] == maple_expansion_device_type_from_index(i); - if (ImGui::Selectable(maple_expansion_device_types[i], &is_selected)) - { - config::MapleExpansionDevices[bus][port] = maple_expansion_device_type_from_index(i); - maple_devices_changed = true; - } - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - } - if (config::MapleMainDevices[bus] == MDT_LightGun) - { - ImGui::TableSetColumnIndex(3); - sprintf(device_name, "##device%d.xhair", bus); - ImguiID _(device_name); - u32 color = config::CrosshairColor[bus]; - float xhairColor[4] { - (color & 0xff) / 255.f, - ((color >> 8) & 0xff) / 255.f, - ((color >> 16) & 0xff) / 255.f, - ((color >> 24) & 0xff) / 255.f - }; - bool colorChanged = ImGui::ColorEdit4("Crosshair color", xhairColor, ImGuiColorEditFlags_AlphaBar | ImGuiColorEditFlags_AlphaPreviewHalf - | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoTooltip | ImGuiColorEditFlags_NoLabel); - ImGui::SameLine(); - bool enabled = color != 0; - if (ImGui::Checkbox("Crosshair", &enabled) || colorChanged) - { - if (enabled) - { - config::CrosshairColor[bus] = (u8)(std::round(xhairColor[0] * 255.f)) - | ((u8)(std::round(xhairColor[1] * 255.f)) << 8) - | ((u8)(std::round(xhairColor[2] * 255.f)) << 16) - | ((u8)(std::round(xhairColor[3] * 255.f)) << 24); - if (config::CrosshairColor[bus] == 0) - config::CrosshairColor[bus] = 0xC0FFFFFF; - } - else - { - config::CrosshairColor[bus] = 0; - } - } - is_there_any_xhair |= enabled; - } - ImGui::PopItemWidth(); - } - ImGui::EndTable(); - } - { - DisabledScope scope(!is_there_any_xhair); - OptionSlider("Crosshair Size", config::CrosshairSize, 10, 100); - } - OptionCheckbox("Per Game VMU A1", config::PerGameVmu, "When enabled, each game has its own VMU on port 1 of controller A."); - } - + gui_settings_controls(maple_devices_changed); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ICON_FA_DISPLAY " Video")) { - int renderApi; - bool perPixel; - switch (config::RendererType) - { - default: - case RenderType::OpenGL: - renderApi = 0; - perPixel = false; - break; - case RenderType::OpenGL_OIT: - renderApi = 0; - perPixel = true; - break; - case RenderType::Vulkan: - renderApi = 1; - perPixel = false; - break; - case RenderType::Vulkan_OIT: - renderApi = 1; - perPixel = true; - break; - case RenderType::DirectX9: - renderApi = 2; - perPixel = false; - break; - case RenderType::DirectX11: - renderApi = 3; - perPixel = false; - break; - case RenderType::DirectX11_OIT: - renderApi = 3; - perPixel = true; - break; - } - ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); - - constexpr int apiCount = 0 - #ifdef USE_VULKAN - + 1 - #endif - #ifdef USE_DX9 - + 1 - #endif - #ifdef USE_OPENGL - + 1 - #endif - #ifdef USE_DX11 - + 1 - #endif - ; - - float innerSpacing = ImGui::GetStyle().ItemInnerSpacing.x; - if (apiCount > 1) - { - header("Graphics API"); - { - ImGui::Columns(apiCount, "renderApi", false); -#ifdef USE_OPENGL - ImGui::RadioButton("OpenGL", &renderApi, 0); - ImGui::NextColumn(); -#endif -#ifdef USE_VULKAN -#ifdef __APPLE__ - ImGui::RadioButton("Vulkan (Metal)", &renderApi, 1); - ImGui::SameLine(0, innerSpacing); - ShowHelpMarker("MoltenVK: An implementation of Vulkan that runs on Apple's Metal graphics framework"); -#else - ImGui::RadioButton("Vulkan", &renderApi, 1); -#endif // __APPLE__ - ImGui::NextColumn(); -#endif -#ifdef USE_DX9 - ImGui::RadioButton("DirectX 9", &renderApi, 2); - ImGui::NextColumn(); -#endif -#ifdef USE_DX11 - ImGui::RadioButton("DirectX 11", &renderApi, 3); - ImGui::NextColumn(); -#endif - ImGui::Columns(1, nullptr, false); - } - } - header("Transparent Sorting"); - { - const bool has_per_pixel = GraphicsContext::Instance()->hasPerPixel(); - int renderer = perPixel ? 2 : config::PerStripSorting ? 1 : 0; - ImGui::Columns(has_per_pixel ? 3 : 2, "renderers", false); - ImGui::RadioButton("Per Triangle", &renderer, 0); - ImGui::SameLine(); - ShowHelpMarker("Sort transparent polygons per triangle. Fast but may produce graphical glitches"); - ImGui::NextColumn(); - ImGui::RadioButton("Per Strip", &renderer, 1); - ImGui::SameLine(); - ShowHelpMarker("Sort transparent polygons per strip. Faster but may produce graphical glitches"); - if (has_per_pixel) - { - ImGui::NextColumn(); - ImGui::RadioButton("Per Pixel", &renderer, 2); - ImGui::SameLine(); - ShowHelpMarker("Sort transparent polygons per pixel. Slower but accurate"); - } - ImGui::Columns(1, NULL, false); - switch (renderer) - { - case 0: - perPixel = false; - config::PerStripSorting.set(false); - break; - case 1: - perPixel = false; - config::PerStripSorting.set(true); - break; - case 2: - perPixel = true; - break; - } - } - ImGui::Spacing(); - - header("Rendering Options"); - { - const std::array scalings{ 0.5f, 1.f, 1.5f, 2.f, 2.5f, 3.f, 4.f, 4.5f, 5.f, 6.f, 7.f, 8.f, 9.f }; - const std::array scalingsText{ "Half", "Native", "x1.5", "x2", "x2.5", "x3", "x4", "x4.5", "x5", "x6", "x7", "x8", "x9" }; - std::array vres; - std::array resLabels; - u32 selected = 0; - for (u32 i = 0; i < scalings.size(); i++) - { - vres[i] = scalings[i] * 480; - if (vres[i] == config::RenderResolution) - selected = i; - if (!config::Widescreen) - resLabels[i] = std::to_string((int)(scalings[i] * 640)) + "x" + std::to_string((int)(scalings[i] * 480)); - else - resLabels[i] = std::to_string((int)(scalings[i] * 480 * 16 / 9)) + "x" + std::to_string((int)(scalings[i] * 480)); - resLabels[i] += " (" + scalingsText[i] + ")"; - } - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); - if (ImGui::BeginCombo("##Resolution", resLabels[selected].c_str(), ImGuiComboFlags_NoArrowButton)) - { - for (u32 i = 0; i < scalings.size(); i++) - { - bool is_selected = vres[i] == config::RenderResolution; - if (ImGui::Selectable(resLabels[i].c_str(), is_selected)) - config::RenderResolution = vres[i]; - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); - ImGui::SameLine(0, innerSpacing); - - if (ImGui::ArrowButton("##Decrease Res", ImGuiDir_Left)) - { - if (selected > 0) - config::RenderResolution = vres[selected - 1]; - } - ImGui::SameLine(0, innerSpacing); - if (ImGui::ArrowButton("##Increase Res", ImGuiDir_Right)) - { - if (selected < vres.size() - 1) - config::RenderResolution = vres[selected + 1]; - } - ImGui::SameLine(0, innerSpacing); - - ImGui::Text("Internal Resolution"); - ImGui::SameLine(); - ShowHelpMarker("Internal render resolution. Higher is better, but more demanding on the GPU. Values higher than your display resolution (but no more than double your display resolution) can be used for supersampling, which provides high-quality antialiasing without reducing sharpness."); - -#ifndef TARGET_IPHONE - OptionCheckbox("VSync", config::VSync, "Synchronizes the frame rate with the screen refresh rate. Recommended"); - if (isVulkan(config::RendererType)) - { - ImGui::Indent(); - { - DisabledScope scope(!config::VSync); - - OptionCheckbox("Duplicate frames", config::DupeFrames, "Duplicate frames on high refresh rate monitors (120 Hz and higher)"); - } - ImGui::Unindent(); - } -#endif - OptionCheckbox("Show VMU In-game", config::FloatVMUs, "Show the VMU LCD screens while in-game"); - OptionCheckbox("Full Framebuffer Emulation", config::EmulateFramebuffer, - "Fully accurate VRAM framebuffer emulation. Helps games that directly access the framebuffer for special effects. " - "Very slow and incompatible with upscaling and wide screen."); - OptionCheckbox("Load Custom Textures", config::CustomTextures, - "Load custom/high-res textures from data/textures/"); - } - ImGui::Spacing(); - header("Aspect Ratio"); - { - OptionCheckbox("Widescreen", config::Widescreen, - "Draw geometry outside of the normal 4:3 aspect ratio. May produce graphical glitches in the revealed areas.\nAspect Fit and shows the full 16:9 content."); - { - DisabledScope scope(!config::Widescreen); - - ImGui::Indent(); - OptionCheckbox("Super Widescreen", config::SuperWidescreen, - "Use the full width of the screen or window when its aspect ratio is greater than 16:9.\nAspect Fill and remove black bars."); - ImGui::Unindent(); - } - OptionCheckbox("Widescreen Game Cheats", config::WidescreenGameHacks, - "Modify the game so that it displays in 16:9 anamorphic format and use horizontal screen stretching. Only some games are supported."); - OptionSlider("Horizontal Stretching", config::ScreenStretching, 100, 250, - "Stretch the screen horizontally", "%d%%"); - OptionCheckbox("Rotate Screen 90°", config::Rotate90, "Rotate the screen 90° counterclockwise"); - } - if (perPixel) - { - ImGui::Spacing(); - header("Per Pixel Settings"); - - const std::array bufSizes{ 512_MB, 1_GB, 2_GB, 4_GB }; - const std::array bufSizesText{ "512 MB", "1 GB", "2 GB", "4 GB" }; - ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); - u32 selected = 0; - for (; selected < bufSizes.size(); selected++) - if (bufSizes[selected] == config::PixelBufferSize) - break; - if (selected == bufSizes.size()) - selected = 0; - if (ImGui::BeginCombo("##PixelBuffer", bufSizesText[selected].c_str(), ImGuiComboFlags_NoArrowButton)) - { - for (u32 i = 0; i < bufSizes.size(); i++) - { - bool is_selected = i == selected; - if (ImGui::Selectable(bufSizesText[i].c_str(), is_selected)) - config::PixelBufferSize = bufSizes[i]; - if (is_selected) { - ImGui::SetItemDefaultFocus(); - selected = i; - } - } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); - ImGui::SameLine(0, innerSpacing); - - if (ImGui::ArrowButton("##Decrease BufSize", ImGuiDir_Left)) - { - if (selected > 0) - config::PixelBufferSize = bufSizes[selected - 1]; - } - ImGui::SameLine(0, innerSpacing); - if (ImGui::ArrowButton("##Increase BufSize", ImGuiDir_Right)) - { - if (selected < bufSizes.size() - 1) - config::PixelBufferSize = bufSizes[selected + 1]; - } - ImGui::SameLine(0, innerSpacing); - - ImGui::Text("Pixel Buffer Size"); - ImGui::SameLine(); - ShowHelpMarker("The size of the pixel buffer. May need to be increased when upscaling by a large factor."); - - OptionSlider("Maximum Layers", config::PerPixelLayers, 8, 128, - "Maximum number of transparent layers. May need to be increased for some complex scenes. Decreasing it may improve performance."); - } - ImGui::Spacing(); - header("Performance"); - { - ImGui::Text("Automatic Frame Skipping:"); - ImGui::Columns(3, "autoskip", false); - OptionRadioButton("Disabled", config::AutoSkipFrame, 0, "No frame skipping"); - ImGui::NextColumn(); - OptionRadioButton("Normal", config::AutoSkipFrame, 1, "Skip a frame when the GPU and CPU are both running slow"); - ImGui::NextColumn(); - OptionRadioButton("Maximum", config::AutoSkipFrame, 2, "Skip a frame when the GPU is running slow"); - ImGui::Columns(1, nullptr, false); - - OptionArrowButtons("Frame Skipping", config::SkipFrame, 0, 6, - "Number of frames to skip between two actually rendered frames"); - OptionCheckbox("Shadows", config::ModifierVolumes, - "Enable modifier volumes, usually used for shadows"); - OptionCheckbox("Fog", config::Fog, "Enable fog effects"); - } - ImGui::Spacing(); - header("Advanced"); - { - OptionCheckbox("Delay Frame Swapping", config::DelayFrameSwapping, - "Useful to avoid flashing screen or glitchy videos. Not recommended on slow platforms"); - OptionCheckbox("Fix Upscale Bleeding Edge", config::FixUpscaleBleedingEdge, - "Helps with texture bleeding case when upscaling. Disabling it can help if pixels are warping when upscaling in 2D games (MVC2, CVS, KOF, etc.)"); - OptionCheckbox("Native Depth Interpolation", config::NativeDepthInterpolation, - "Helps with texture corruption and depth issues on AMD GPUs. Can also help Intel GPUs in some cases."); - OptionCheckbox("Copy Rendered Textures to VRAM", config::RenderToTextureBuffer, - "Copy rendered-to textures back to VRAM. Slower but accurate"); - const std::array aniso{ 1, 2, 4, 8, 16 }; - const std::array anisoText{ "Disabled", "2x", "4x", "8x", "16x" }; - u32 afSelected = 0; - for (u32 i = 0; i < aniso.size(); i++) - { - if (aniso[i] == config::AnisotropicFiltering) - afSelected = i; - } - - ImGui::PushItemWidth(ImGui::CalcItemWidth() - innerSpacing * 2.0f - ImGui::GetFrameHeight() * 2.0f); - if (ImGui::BeginCombo("##Anisotropic Filtering", anisoText[afSelected].c_str(), ImGuiComboFlags_NoArrowButton)) - { - for (u32 i = 0; i < aniso.size(); i++) - { - bool is_selected = aniso[i] == config::AnisotropicFiltering; - if (ImGui::Selectable(anisoText[i].c_str(), is_selected)) - config::AnisotropicFiltering = aniso[i]; - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); - ImGui::SameLine(0, innerSpacing); - - if (ImGui::ArrowButton("##Decrease Anisotropic Filtering", ImGuiDir_Left)) - { - if (afSelected > 0) - config::AnisotropicFiltering = aniso[afSelected - 1]; - } - ImGui::SameLine(0, innerSpacing); - if (ImGui::ArrowButton("##Increase Anisotropic Filtering", ImGuiDir_Right)) - { - if (afSelected < aniso.size() - 1) - config::AnisotropicFiltering = aniso[afSelected + 1]; - } - ImGui::SameLine(0, innerSpacing); - - ImGui::Text("Anisotropic Filtering"); - ImGui::SameLine(); - ShowHelpMarker("Higher values make textures viewed at oblique angles look sharper, but are more demanding on the GPU. This option only has a visible impact on mipmapped textures."); - - ImGui::Text("Texture Filtering:"); - ImGui::Columns(3, "textureFiltering", false); - OptionRadioButton("Default", config::TextureFiltering, 0, "Use the game's default texture filtering"); - ImGui::NextColumn(); - OptionRadioButton("Force Nearest-Neighbor", config::TextureFiltering, 1, "Force nearest-neighbor filtering for all textures. Crisper appearance, but may cause various rendering issues. This option usually does not affect performance."); - ImGui::NextColumn(); - OptionRadioButton("Force Linear", config::TextureFiltering, 2, "Force linear filtering for all textures. Smoother appearance, but may cause various rendering issues. This option usually does not affect performance."); - ImGui::Columns(1, nullptr, false); - - OptionCheckbox("Show FPS Counter", config::ShowFPS, "Show on-screen frame/sec counter"); - } - ImGui::Spacing(); - header("Texture Upscaling"); - { -#ifdef _OPENMP - OptionArrowButtons("Texture Upscaling", config::TextureUpscale, 1, 8, - "Upscale textures with the xBRZ algorithm. Only on fast platforms and for certain 2D games", "x%d"); - OptionSlider("Texture Max Size", config::MaxFilteredTextureSize, 8, 1024, - "Textures larger than this dimension squared will not be upscaled"); - OptionArrowButtons("Max Threads", config::MaxThreads, 1, 8, - "Maximum number of threads to use for texture upscaling. Recommended: number of physical cores minus one"); -#endif - } -#ifdef VIDEO_ROUTING -#ifdef __APPLE__ - header("Video Routing (Syphon)"); -#elif defined(_WIN32) - ((renderApi == 0) || (renderApi == 3)) ? header("Video Routing (Spout)") : header("Video Routing (Only available with OpenGL or DirectX 11)"); -#endif - { -#ifdef _WIN32 - DisabledScope scope(!((renderApi == 0) || (renderApi == 3))); -#endif - OptionCheckbox("Send video content to another program", config::VideoRouting, - "e.g. Route GPU texture to OBS Studio directly instead of using CPU intensive Display/Window Capture"); - - { - DisabledScope scope(!config::VideoRouting); - OptionCheckbox("Scale down before sending", config::VideoRoutingScale, "Could increase performance when sharing a smaller texture, YMMV"); - { - DisabledScope scope(!config::VideoRoutingScale); - static int vres = config::VideoRoutingVRes; - if (ImGui::InputInt("Output vertical resolution", &vres)) - { - config::VideoRoutingVRes = vres; - } - } - ImGui::Text("Output texture size: %d x %d", config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width, config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height); - } - } -#endif + gui_settings_video(); ImGui::EndTabItem(); - - switch (renderApi) - { - case 0: - config::RendererType = perPixel ? RenderType::OpenGL_OIT : RenderType::OpenGL; - break; - case 1: - config::RendererType = perPixel ? RenderType::Vulkan_OIT : RenderType::Vulkan; - break; - case 2: - config::RendererType = RenderType::DirectX9; - break; - case 3: - config::RendererType = perPixel ? RenderType::DirectX11_OIT : RenderType::DirectX11; - break; - } } - if (ImGui::BeginTabItem(ICON_FA_MUSIC " Audio")) // or ICON_FA_VOLUME_OFF? + if (ImGui::BeginTabItem(ICON_FA_MUSIC " Audio")) { ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); - OptionCheckbox("Enable DSP", config::DSPEnabled, - "Enable the Dreamcast Digital Sound Processor. Only recommended on fast platforms"); - OptionCheckbox("Enable VMU Sounds", config::VmuSound, "Play VMU beeps when enabled."); - - if (OptionSlider("Volume Level", config::AudioVolume, 0, 100, "Adjust the emulator's audio level", "%d%%")) - { - config::AudioVolume.calcDbPower(); - }; -#ifdef __ANDROID__ - if (config::AudioBackend.get() == "auto" || config::AudioBackend.get() == "android") - OptionCheckbox("Automatic Latency", config::AutoLatency, - "Automatically set audio latency. Recommended"); -#endif - if (!config::AutoLatency - || (config::AudioBackend.get() != "auto" && config::AudioBackend.get() != "android")) - { - int latency = (int)roundf(config::AudioBufferSize * 1000.f / 44100.f); - ImGui::SliderInt("Latency", &latency, 12, 512, "%d ms"); - config::AudioBufferSize = (int)roundf(latency * 44100.f / 1000.f); - ImGui::SameLine(); - ShowHelpMarker("Sets the maximum audio latency. Not supported by all audio drivers."); - } - - AudioBackend *backend = nullptr; - std::string backend_name = config::AudioBackend; - if (backend_name != "auto") - { - backend = AudioBackend::getBackend(config::AudioBackend); - if (backend != nullptr) - backend_name = backend->slug; - } - - AudioBackend *current_backend = backend; - if (ImGui::BeginCombo("Audio Driver", backend_name.c_str(), ImGuiComboFlags_None)) - { - bool is_selected = (config::AudioBackend.get() == "auto"); - if (ImGui::Selectable("auto - Automatic driver selection", &is_selected)) - config::AudioBackend.set("auto"); - - for (u32 i = 0; i < AudioBackend::getCount(); i++) - { - backend = AudioBackend::getBackend(i); - is_selected = (config::AudioBackend.get() == backend->slug); - - if (is_selected) - current_backend = backend; - - if (ImGui::Selectable((backend->slug + " - " + backend->name).c_str(), &is_selected)) - config::AudioBackend.set(backend->slug); - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - ImGui::SameLine(); - ShowHelpMarker("The audio driver to use"); - - if (current_backend != nullptr) - { - // get backend specific options - int option_count; - const AudioBackend::Option *options = current_backend->getOptions(&option_count); - - for (int o = 0; o < option_count; o++) - { - std::string value = cfgLoadStr(current_backend->slug, options->name, ""); - - if (options->type == AudioBackend::Option::integer) - { - int val = stoi(value); - if (ImGui::SliderInt(options->caption.c_str(), &val, options->minValue, options->maxValue)) - { - std::string s = std::to_string(val); - cfgSaveStr(current_backend->slug, options->name, s); - } - } - else if (options->type == AudioBackend::Option::checkbox) - { - bool check = value == "1"; - if (ImGui::Checkbox(options->caption.c_str(), &check)) - cfgSaveStr(current_backend->slug, options->name, - check ? "1" : "0"); - } - else if (options->type == AudioBackend::Option::list) - { - if (ImGui::BeginCombo(options->caption.c_str(), value.c_str(), ImGuiComboFlags_None)) - { - bool is_selected = false; - for (const auto& cur : options->values) - { - is_selected = value == cur; - if (ImGui::Selectable(cur.c_str(), &is_selected)) - cfgSaveStr(current_backend->slug, options->name, cur); - - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - } - else { - WARN_LOG(RENDERER, "Unknown option"); - } - - options++; - } - } - + gui_settings_audio(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ICON_FA_WIFI " Network")) { - ImGuiStyle& style = ImGui::GetStyle(); ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); - - header("Network Type"); - { - DisabledScope scope(game_started); - - int netType = 0; - if (config::GGPOEnable) - netType = 1; - else if (config::NetworkEnable) - netType = 2; - else if (config::BattleCableEnable) - netType = 3; - ImGui::Columns(4, "networkType", false); - ImGui::RadioButton("Disabled", &netType, 0); - ImGui::NextColumn(); - ImGui::RadioButton("GGPO", &netType, 1); - ImGui::SameLine(0, style.ItemInnerSpacing.x); - ShowHelpMarker("Enable networking using GGPO"); - ImGui::NextColumn(); - ImGui::RadioButton("Naomi", &netType, 2); - ImGui::SameLine(0, style.ItemInnerSpacing.x); - ShowHelpMarker("Enable networking for supported Naomi and Atomiswave games"); - ImGui::NextColumn(); - ImGui::RadioButton("Battle Cable", &netType, 3); - ImGui::SameLine(0, style.ItemInnerSpacing.x); - ShowHelpMarker("Emulate the Taisen (Battle) null modem cable for games that support it"); - ImGui::Columns(1, nullptr, false); - - config::GGPOEnable = false; - config::NetworkEnable = false; - config::BattleCableEnable = false; - switch (netType) { - case 1: - config::GGPOEnable = true; - break; - case 2: - config::NetworkEnable = true; - break; - case 3: - config::BattleCableEnable = true; - break; - } - } - if (config::GGPOEnable || config::NetworkEnable || config::BattleCableEnable) { - ImGui::Spacing(); - header("Configuration"); - } - { - if (config::GGPOEnable) - { - config::NetworkEnable = false; - OptionCheckbox("Play as Player 1", config::ActAsServer, - "Deselect to play as player 2"); - ImGui::InputText("Peer", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); - ImGui::SameLine(); - ShowHelpMarker("Your peer IP address and optional port"); - OptionSlider("Frame Delay", config::GGPODelay, 0, 20, - "Sets Frame Delay, advisable for sessions with ping >100 ms"); - - ImGui::Text("Left Thumbstick:"); - OptionRadioButton("Disabled", config::GGPOAnalogAxes, 0, "Left thumbstick not used"); - ImGui::SameLine(); - OptionRadioButton("Horizontal", config::GGPOAnalogAxes, 1, "Use the left thumbstick horizontal axis only"); - ImGui::SameLine(); - OptionRadioButton("Full", config::GGPOAnalogAxes, 2, "Use the left thumbstick horizontal and vertical axes"); - - OptionCheckbox("Enable Chat", config::GGPOChat, "Open the chat window when a chat message is received"); - if (config::GGPOChat) - { - OptionCheckbox("Enable Chat Window Timeout", config::GGPOChatTimeoutToggle, "Automatically close chat window after 20 seconds"); - if (config::GGPOChatTimeoutToggle) - { - char chatTimeout[256]; - sprintf(chatTimeout, "%d", (int)config::GGPOChatTimeout); - ImGui::InputText("Chat Window Timeout (seconds)", chatTimeout, sizeof(chatTimeout), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); - ImGui::SameLine(); - ShowHelpMarker("Sets duration that chat window stays open after new message is received."); - config::GGPOChatTimeout.set(atoi(chatTimeout)); - } - } - OptionCheckbox("Network Statistics", config::NetworkStats, - "Display network statistics on screen"); - } - else if (config::NetworkEnable) - { - OptionCheckbox("Act as Server", config::ActAsServer, - "Create a local server for Naomi network games"); - if (!config::ActAsServer) - { - ImGui::InputText("Server", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); - ImGui::SameLine(); - ShowHelpMarker("The server to connect to. Leave blank to find a server automatically on the default port"); - } - char localPort[256]; - sprintf(localPort, "%d", (int)config::LocalPort); - ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); - ImGui::SameLine(); - ShowHelpMarker("The local UDP port to use"); - config::LocalPort.set(atoi(localPort)); - } - else if (config::BattleCableEnable) - { - ImGui::InputText("Peer", &config::NetworkServer.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); - ImGui::SameLine(); - ShowHelpMarker("The peer to connect to. Leave blank to find a player automatically on the default port"); - char localPort[256]; - sprintf(localPort, "%d", (int)config::LocalPort); - ImGui::InputText("Local Port", localPort, sizeof(localPort), ImGuiInputTextFlags_CharsDecimal, nullptr, nullptr); - ImGui::SameLine(); - ShowHelpMarker("The local UDP port to use"); - config::LocalPort.set(atoi(localPort)); - } - } - ImGui::Spacing(); - header("Network Options"); - { - OptionCheckbox("Enable UPnP", config::EnableUPnP, "Automatically configure your network router for netplay"); - OptionCheckbox("Broadcast Digital Outputs", config::NetworkOutput, "Broadcast digital outputs and force-feedback state on TCP port 8000. " - "Compatible with the \"-output network\" MAME option. Arcade games only."); - { - DisabledScope scope(game_started); - - OptionCheckbox("Broadband Adapter Emulation", config::EmulateBBA, - "Emulate the Ethernet Broadband Adapter (BBA) instead of the Modem"); - } - } -#ifdef NAOMI_MULTIBOARD - ImGui::Spacing(); - header("Multiboard Screens"); - { - //OptionRadioButton("Disabled", config::MultiboardSlaves, 0, "Multiboard disabled (when optional)"); - OptionRadioButton("1 (Twin)", config::MultiboardSlaves, 1, "One screen configuration (F355 Twin)"); - ImGui::SameLine(); - OptionRadioButton("3 (Deluxe)", config::MultiboardSlaves, 2, "Three screens configuration"); - } -#endif + gui_settings_network(); ImGui::EndTabItem(); } if (ImGui::BeginTabItem(ICON_FA_MICROCHIP " Advanced")) { ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); - header("CPU Mode"); - { - ImGui::Columns(2, "cpu_modes", false); - OptionRadioButton("Dynarec", config::DynarecEnabled, true, - "Use the dynamic recompiler. Recommended in most cases"); - ImGui::NextColumn(); - OptionRadioButton("Interpreter", config::DynarecEnabled, false, - "Use the interpreter. Very slow but may help in case of a dynarec problem"); - ImGui::Columns(1, NULL, false); - - OptionSlider("SH4 Clock", config::Sh4Clock, 100, 300, - "Over/Underclock the main SH4 CPU. Default is 200 MHz. Other values may crash, freeze or trigger unexpected nuclear reactions.", - "%d MHz"); - } - ImGui::Spacing(); - header("Other"); - { - OptionCheckbox("HLE BIOS", config::UseReios, "Force high-level BIOS emulation"); - OptionCheckbox("Multi-threaded emulation", config::ThreadedRendering, - "Run the emulated CPU and GPU on different threads"); -#ifndef __ANDROID - OptionCheckbox("Serial Console", config::SerialConsole, - "Dump the Dreamcast serial console to stdout"); -#endif - { - DisabledScope scope(game_started); - OptionCheckbox("Dreamcast 32MB RAM Mod", config::RamMod32MB, - "Enables 32MB RAM Mod for Dreamcast. May affect compatibility"); - } - OptionCheckbox("Dump Textures", config::DumpTextures, - "Dump all textures into data/texdump/"); - - bool logToFile = cfgLoadBool("log", "LogToFile", false); - bool newLogToFile = logToFile; - ImGui::Checkbox("Log to File", &newLogToFile); - if (logToFile != newLogToFile) - { - cfgSaveBool("log", "LogToFile", newLogToFile); - LogManager::Shutdown(); - LogManager::Init(); - } - ImGui::SameLine(); - ShowHelpMarker("Log debug information to flycast.log"); -#ifdef SENTRY_UPLOAD - OptionCheckbox("Automatically Report Crashes", config::UploadCrashLogs, - "Automatically upload crash reports to sentry.io to help in troubleshooting. No personal information is included."); -#endif - } - -#ifdef USE_LUA - header("Lua Scripting"); - { - ImGui::InputText("Lua Filename", &config::LuaFileName.get(), ImGuiInputTextFlags_CharsNoBlank, nullptr, nullptr); - ImGui::SameLine(); - ShowHelpMarker("Specify lua filename to use. Should be located in Flycast config directory. Defaults to flycast.lua when empty."); - } -#endif + gui_settings_advanced(); ImGui::EndTabItem(); } - #if !defined(NDEBUG) || defined(DEBUGFAST) || FC_PROFILER - gui_debug_tab(normal_padding); + if (ImGui::BeginTabItem(ICON_FA_BUG " Debug")) + { + ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); + gui_debug_tab(); + ImGui::EndTabItem(); + } #endif - if (ImGui::BeginTabItem(ICON_FA_CIRCLE_INFO " About")) { ImguiStyleVar _(ImGuiStyleVar_FramePadding, normal_padding); - header("Flycast"); - { - ImGui::Text("Version: %s", GIT_VERSION); - ImGui::Text("Git Hash: %s", GIT_HASH); - ImGui::Text("Build Date: %s", BUILD_DATE); - } - ImGui::Spacing(); - header("Platform"); - { - ImGui::Text("CPU: %s", -#if HOST_CPU == CPU_X86 - "x86" -#elif HOST_CPU == CPU_ARM - "ARM" -#elif HOST_CPU == CPU_MIPS - "MIPS" -#elif HOST_CPU == CPU_X64 - "x86/64" -#elif HOST_CPU == CPU_GENERIC - "Generic" -#elif HOST_CPU == CPU_ARM64 - "ARM64" -#else - "Unknown" -#endif - ); - ImGui::Text("Operating System: %s", -#ifdef __ANDROID__ - "Android" -#elif defined(__unix__) - "Linux" -#elif defined(__APPLE__) -#ifdef TARGET_IPHONE - "iOS" -#else - "macOS" -#endif -#elif defined(TARGET_UWP) - "Windows Universal Platform" -#elif defined(_WIN32) - "Windows" -#elif defined(__SWITCH__) - "Switch" -#else - "Unknown" -#endif - ); -#ifdef TARGET_IPHONE - const char *getIosJitStatus(); - ImGui::Text("JIT Status: %s", getIosJitStatus()); -#endif - } - ImGui::Spacing(); - if (isOpenGL(config::RendererType)) - header("OpenGL"); - else if (isVulkan(config::RendererType)) - header("Vulkan"); - else if (isDirectX(config::RendererType)) - header("DirectX"); - ImGui::Text("Driver Name: %s", GraphicsContext::Instance()->getDriverName().c_str()); - ImGui::Text("Version: %s", GraphicsContext::Instance()->getDriverVersion().c_str()); - -#if defined(__ANDROID__) && HOST_CPU == CPU_ARM64 && USE_VULKAN - if (isVulkan(config::RendererType)) - { - ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 10)); - if (config::CustomGpuDriver) - { - std::string name, description, vendor, version; - if (getCustomGpuDriverInfo(name, description, vendor, version)) - { - ImGui::Text("Custom Driver:"); - ImGui::Indent(); - ImGui::Text("%s - %s", name.c_str(), description.c_str()); - ImGui::Text("%s - %s", vendor.c_str(), version.c_str()); - ImGui::Unindent(); - } - - if (ImGui::Button("Use Default Driver")) { - config::CustomGpuDriver = false; - ImGui::OpenPopup("Reset Vulkan"); - } - } - else if (ImGui::Button("Upload Custom Driver")) - ImGui::OpenPopup("Select custom GPU driver"); - - static bool driverDirty; - const auto& callback = [](bool cancelled, std::string selection) { - if (!cancelled) { - try { - uploadCustomGpuDriver(selection); - config::CustomGpuDriver = true; - driverDirty = true; - } catch (const FlycastException& e) { - gui_error(e.what()); - config::CustomGpuDriver = false; - } - } - return true; - }; - select_file_popup("Select custom GPU driver", callback, true, "zip"); - - if (driverDirty) { - ImGui::OpenPopup("Reset Vulkan"); - driverDirty = false; - } - - ImguiStyleVar _1(ImGuiStyleVar_WindowPadding, ScaledVec2(20, 20)); - if (ImGui::BeginPopupModal("Reset Vulkan", NULL, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoScrollbar)) - { - ImGui::Text("Do you want to reset Vulkan to use new driver?"); - ImGui::NewLine(); - ImguiStyleVar _(ImGuiStyleVar_ItemSpacing, ImVec2(uiScaled(20), ImGui::GetStyle().ItemSpacing.y)); - ImguiStyleVar _1(ImGuiStyleVar_FramePadding, ScaledVec2(10, 10)); - if (ImGui::Button("Yes")) - { - mainui_reinit(); - ImGui::CloseCurrentPopup(); - } - ImGui::SameLine(); - if (ImGui::Button("No")) - ImGui::CloseCurrentPopup(); - ImGui::EndPopup(); - } - } -#endif + gui_settings_about(); ImGui::EndTabItem(); } ImGui::EndTabBar(); From f76d05a3d57d307584265cac3c5d726bcc3c4b3b Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 7 May 2024 17:50:36 +0200 Subject: [PATCH 64/86] achievements: leaderboard notifications. detailed toast message Draw achievement notifications using imgui drawlist api Fixes for insets Issue #761 --- core/achievements/achievements.cpp | 64 +++++++++- core/emulator.cpp | 2 +- core/hw/naomi/printer.cpp | 5 +- core/rend/gui.cpp | 8 +- core/rend/gui.h | 2 +- core/rend/gui_achievements.cpp | 193 ++++++++++++++++++++--------- core/rend/gui_achievements.h | 7 +- core/rend/gui_util.cpp | 26 +++- core/rend/gui_util.h | 1 + shell/libretro/libretro.cpp | 4 +- 10 files changed, 231 insertions(+), 81 deletions(-) diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp index 431839356..e5db54f7c 100644 --- a/core/achievements/achievements.cpp +++ b/core/achievements/achievements.cpp @@ -89,6 +89,12 @@ private: void handleShowAchievementProgress(const rc_client_event_t *event); void handleHideAchievementProgress(const rc_client_event_t *event); void handleUpdateAchievementProgress(const rc_client_event_t *event); + void handleLeaderboardStarted(const rc_client_event_t *event); + void handleLeaderboardFailed(const rc_client_event_t *event); + void handleLeaderboardSubmitted(const rc_client_event_t *event); + void handleShowLeaderboardTracker(const rc_client_event_t *event); + void handleHideLeaderboardTracker(const rc_client_event_t *event); + void handleUpdateLeaderboardTracker(const rc_client_event_t *event); static void emuEventCallback(Event event, void *arg); rc_client_t *rc_client = nullptr; @@ -506,16 +512,26 @@ void Achievements::clientEventHandler(const rc_client_event_t* event, rc_client_ achievements->handleUpdateAchievementProgress(event); break; -/* - TODO case RC_CLIENT_EVENT_LEADERBOARD_STARTED: + achievements->handleLeaderboardStarted(event); + break; case RC_CLIENT_EVENT_LEADERBOARD_FAILED: + achievements->handleLeaderboardFailed(event); + break; case RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED: - case RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD: + achievements->handleLeaderboardSubmitted(event); + break; case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW: + achievements->handleShowLeaderboardTracker(event); + break; case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE: + achievements->handleHideLeaderboardTracker(event); + break; case RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE: -*/ + achievements->handleUpdateLeaderboardTracker(event); + break; +// TODO case RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD: + case RC_CLIENT_EVENT_DISCONNECTED: notifyError("RetroAchievements disconnected"); break; @@ -578,6 +594,46 @@ void Achievements::handleAchievementChallengeIndicatorHideEvent(const rc_client_ } } +void Achievements::handleLeaderboardStarted(const rc_client_event_t *event) +{ + const rc_client_leaderboard_t *leaderboard = event->leaderboard; + INFO_LOG(COMMON, "RA: Leaderboard started: %s", leaderboard->title); + std::string text = "Leaderboard " + std::string(leaderboard->title) + " started"; + notifier.notify(Notification::Unlocked, "", text, leaderboard->description); +} +void Achievements::handleLeaderboardFailed(const rc_client_event_t *event) +{ + const rc_client_leaderboard_t *leaderboard = event->leaderboard; + INFO_LOG(COMMON, "RA: Leaderboard failed: %s", leaderboard->title); + std::string text = "Leaderboard " + std::string(leaderboard->title) + " failed"; + notifier.notify(Notification::Unlocked, "", text, leaderboard->description); +} +void Achievements::handleLeaderboardSubmitted(const rc_client_event_t *event) +{ + const rc_client_leaderboard_t *leaderboard = event->leaderboard; + INFO_LOG(COMMON, "RA: Leaderboard submitted: %s", leaderboard->title); + std::string text = "Leaderboard " + std::string(leaderboard->title) + " submitted"; + notifier.notify(Notification::Unlocked, "", text, leaderboard->description); +} +void Achievements::handleShowLeaderboardTracker(const rc_client_event_t *event) +{ + const rc_client_leaderboard_tracker_t *leaderboard = event->leaderboard_tracker; + DEBUG_LOG(COMMON, "RA: Show leaderboard[%d]: %s", leaderboard->id, leaderboard->display); + notifier.showLeaderboard(leaderboard->id, leaderboard->display); +} +void Achievements::handleHideLeaderboardTracker(const rc_client_event_t *event) +{ + const rc_client_leaderboard_tracker_t *leaderboard = event->leaderboard_tracker; + DEBUG_LOG(COMMON, "RA: Hide leaderboard[%d]: %s", leaderboard->id, leaderboard->display); + notifier.hideLeaderboard(leaderboard->id); +} +void Achievements::handleUpdateLeaderboardTracker(const rc_client_event_t *event) +{ + const rc_client_leaderboard_tracker_t *leaderboard = event->leaderboard_tracker; + DEBUG_LOG(COMMON, "RA: Update leaderboard[%d]: %s", leaderboard->id, leaderboard->display); + notifier.showLeaderboard(leaderboard->id, leaderboard->display); +} + void Achievements::handleGameCompleted(const rc_client_event_t *event) { const rc_client_game_t* game = rc_client_get_game_info(rc_client); diff --git a/core/emulator.cpp b/core/emulator.cpp index 7fa51ff9d..acb7c9b10 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -541,7 +541,7 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) cheatManager.reset(settings.content.gameId); if (cheatManager.isWidescreen()) { - gui_display_notification("Widescreen cheat activated", 1000); + gui_display_notification("Widescreen cheat activated", 2000); config::ScreenStretching.override(134); // 4:3 -> 16:9 } // reload settings so that all settings can be overridden diff --git a/core/hw/naomi/printer.cpp b/core/hw/naomi/printer.cpp index df7551307..29eb0db81 100644 --- a/core/hw/naomi/printer.cpp +++ b/core/hw/naomi/printer.cpp @@ -830,8 +830,7 @@ private: std::string s = get_writable_data_path(settings.content.gameId + "-results.png"); bitmapWriter->save(s); bitmapWriter.reset(); - s = "Print out saved to " + s; - gui_display_notification(s.c_str(), 5000); + gui_display_notification("Print out saved", 5000, s.c_str()); NOTICE_LOG(NAOMI, "%s", s.c_str()); } break; @@ -1198,7 +1197,7 @@ std::string get_writable_data_path(const std::string& s) return "./" + s; } -void gui_display_notification(char const*, int) { +void gui_display_notification(char const*, int, char const*) { } int main(int argc, char *argv[]) diff --git a/core/rend/gui.cpp b/core/rend/gui.cpp index a6a0cef79..424c7ff2e 100644 --- a/core/rend/gui.cpp +++ b/core/rend/gui.cpp @@ -2971,7 +2971,7 @@ static void gui_display_settings() ImGui::End(); } -void gui_display_notification(const char *msg, int duration) +void gui_display_notification(const char *msg, int duration, const char *details) { if (gui_state != GuiState::Closed) { @@ -2980,7 +2980,7 @@ void gui_display_notification(const char *msg, int duration) osd_message_end = getTimeMs() + duration; } else { - toast.show(msg, "", duration); + toast.show(msg, details != nullptr ? details : "", duration); } } @@ -3465,7 +3465,7 @@ void gui_display_osd() const ScaledVec2 padding(5.f, 5.f); const ImVec2 size = largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, maxW, &message.front(), &message.back() + 1) + padding * 2.f; - ImVec2 pos(0, ImGui::GetIO().DisplaySize.y - size.y); + ImVec2 pos(insetLeft, ImGui::GetIO().DisplaySize.y - size.y); constexpr float alpha = 0.7f; const ImU32 bg_col = alphaOverride(0x00202020, alpha / 2.f); dl->AddRectFilled(pos, pos + size, bg_col, 0.f); @@ -3560,7 +3560,7 @@ void fatal_error(const char* text, ...) va_end(args); ERROR_LOG(COMMON, "%s", temp); - gui_display_notification(temp, 2000); + gui_display_notification("Fatal Error", 20000, temp); } extern bool subfolders_read; diff --git a/core/rend/gui.h b/core/rend/gui.h index d0ef7af84..593ba32b8 100644 --- a/core/rend/gui.h +++ b/core/rend/gui.h @@ -25,7 +25,7 @@ void gui_init(); void gui_initFonts(); void gui_open_settings(); void gui_display_ui(); -void gui_display_notification(const char *msg, int duration); +void gui_display_notification(const char *msg, int duration, const char *details = nullptr); void gui_display_osd(); void gui_display_profiler(); void gui_open_onboarding(); diff --git a/core/rend/gui_achievements.cpp b/core/rend/gui_achievements.cpp index e5d42fe63..f9aa436e1 100644 --- a/core/rend/gui_achievements.cpp +++ b/core/rend/gui_achievements.cpp @@ -28,6 +28,7 @@ #include extern ImFont *largeFont; +extern int insetLeft; namespace achievements { @@ -42,6 +43,7 @@ static constexpr u64 NEVER_ENDS = 1000000000000; void Notification::notify(Type type, const std::string& image, const std::string& text1, const std::string& text2, const std::string& text3) { + verify(type != Challenge && type != Leaderboard); std::lock_guard _(mutex); u64 now = getTimeMs(); if (type == Progress) @@ -98,6 +100,36 @@ void Notification::hideChallenge(const std::string& image) endTime = getTimeMs(); } +void Notification::showLeaderboard(u32 id, const std::string& text) +{ + std::lock_guard _(mutex); + auto it = leaderboards.find(id); + if (it == leaderboards.end()) + { + if (leaderboards.empty()) + { + this->type = Leaderboard; + startTime = getTimeMs(); + endTime = NEVER_ENDS; + } + leaderboards[id] = text; + } + else { + it->second = text; + } +} + +void Notification::hideLeaderboard(u32 id) +{ + std::lock_guard _(mutex); + auto it = leaderboards.find(id); + if (it == leaderboards.end()) + return; + leaderboards.erase(it); + if (this->type == Leaderboard && leaderboards.empty()) + endTime = getTimeMs(); +} + bool Notification::draw() { std::lock_guard _(mutex); @@ -106,7 +138,14 @@ bool Notification::draw() u64 now = getTimeMs(); if (now > endTime + END_ANIM_TIME) { - if (!challenges.empty()) + if (!leaderboards.empty()) + { + // Show current leaderboards + type = Leaderboard; + startTime = getTimeMs(); + endTime = NEVER_ENDS; + } + else if (!challenges.empty()) { // Show current challenge indicators type = Challenge; @@ -120,78 +159,114 @@ bool Notification::draw() return false; } } + float alpha = 1.f; if (now > endTime) - { // Fade out - float alpha = (std::cos((now - endTime) / (float)END_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; - ImGui::GetStyle().Alpha = alpha; - ImGui::SetNextWindowBgAlpha(alpha / 2.f); - } - else { - ImGui::SetNextWindowBgAlpha(0.5f); - } - float y = ImGui::GetIO().DisplaySize.y; + alpha = (std::cos((now - endTime) / (float)END_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; + float animY = 0.f; if (now - startTime < START_ANIM_TIME) // Slide up - y += uiScaled(80.f) * (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; + animY = (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; - ImGui::SetNextWindowPos(ImVec2(0, y), ImGuiCond_Always, ImVec2(0.f, 1.f)); // Lower left corner + const ImVec2 padding = ImGui::GetStyle().WindowPadding; + ImDrawList *dl = ImGui::GetForegroundDrawList(); + const ImU32 bg_col = alphaOverride(ImGui::GetColorU32(ImGuiCol_WindowBg), alpha / 2.f); + const ImU32 borderCol = alphaOverride(ImGui::GetColorU32(ImGuiCol_Border), alpha); if (type == Challenge) { - ImGui::Begin("##achievement", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav - | ImGuiWindowFlags_NoInputs); - for (const auto& img : challenges) - { - img.draw(ScaledVec2(60.f, 60.f)); - ImGui::SameLine(); + const ScaledVec2 size(60.f, 60.f); + const ImVec2 spacing(ImGui::GetStyle().ItemSpacing.x, 0.f); + const ImVec2 totalSize = size * challenges.size() + spacing * (challenges.size() - 1) + padding * 2.f; + ImVec2 pos(insetLeft, ImGui::GetIO().DisplaySize.y - totalSize.y * (1.f - animY)); + dl->AddRectFilled(pos, pos + totalSize, bg_col, 0.f); + dl->AddRect(pos, pos + totalSize, borderCol, 0.f); + + pos += padding; + for (const auto& img : challenges) { + img.draw(dl, pos, size, alpha); + pos += spacing; + } + } + else if (type == Leaderboard) + { + ImFont *font = ImGui::GetFont(); + const ImVec2 padding = ImGui::GetStyle().FramePadding; + // iterate from the end + ImVec2 pos(insetLeft + padding.x, ImGui::GetIO().DisplaySize.y - padding.y); + for (auto it = leaderboards.rbegin(); it != leaderboards.rend(); ++it) + { + const std::string& text = it->second; + ImVec2 size = font->CalcTextSizeA(font->FontSize, FLT_MAX, -1.f, text.c_str()); + ImVec2 psize = size + padding * 2; + pos.y -= psize.y; + dl->AddRectFilled(pos, pos + psize, bg_col, 0.f); + ImVec2 tpos = pos + padding; + const ImU32 col = alphaOverride(0xffffff, alpha); + dl->AddText(font, font->FontSize, tpos, col, &text.front(), &text.back() + 1, FLT_MAX); + pos.y -= padding.y; } - ImGui::End(); } else { - ImGui::SetNextWindowSizeConstraints(ScaledVec2(80.f, 80.f) + ImVec2(ImGui::GetStyle().WindowPadding.x * 2, 0.f), ImVec2(FLT_MAX, FLT_MAX)); - const float winPaddingX = ImGui::GetStyle().WindowPadding.x; - ImguiStyleVar _(ImGuiStyleVar_WindowPadding, ImVec2{}); - - ImGui::Begin("##achievement", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav - | ImGuiWindowFlags_NoInputs); - ImTextureID imageId = image.getId(); - const bool hasPic = imageId != ImTextureID{}; - if (ImGui::BeginTable("achievementNotif", hasPic ? 2 : 1, ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings)) + const float hspacing = ImGui::GetStyle().ItemSpacing.x; + const float vspacing = ImGui::GetStyle().ItemSpacing.y; + const ScaledVec2 imgSize = image.getId() != ImTextureID{} ? ScaledVec2(80.f, 80.f) : ScaledVec2(); + // text size + const float maxW = std::min(ImGui::GetIO().DisplaySize.x, uiScaled(640.f)) - padding.x + - (imgSize.x != 0.f ? imgSize.x + hspacing : padding.x); + ImFont *regularFont = ImGui::GetFont(); + ImVec2 textSize[3] {}; + ImVec2 totalSize(0.f, padding.y * 2); + for (size_t i = 0; i < std::size(text); i++) { - if (hasPic) - ImGui::TableSetupColumn("icon", ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("text", ImGuiTableColumnFlags_WidthStretch); - - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); - if (hasPic) - { - image.draw(ScaledVec2(80.f, 80.f)); - ImGui::TableSetColumnIndex(1); - } - - float w = largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, -1.f, text[0].c_str()).x; - w = std::max(w, ImGui::CalcTextSize(text[1].c_str()).x); - w = std::max(w, ImGui::CalcTextSize(text[2].c_str()).x) + winPaddingX * 2; - int lines = (int)!text[0].empty() + (int)!text[1].empty() + (int)!text[2].empty(); - ImguiStyleVar _(ImGuiStyleVar_WindowPadding, ImVec2{ hasPic ? 0.f : winPaddingX, (3 - lines) * ImGui::GetTextLineHeight() / 2 }); - if (ImGui::BeginChild("##text", ImVec2(w, 0), ImGuiChildFlags_AlwaysUseWindowPadding, ImGuiWindowFlags_None)) - { - ImGui::PushFont(largeFont); - ImGui::Text("%s", text[0].c_str()); - ImGui::PopFont(); - if (!text[1].empty()) - ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", text[1].c_str()); - if (!text[2].empty()) - ImGui::TextColored(ImVec4(1, 1, 0, 0.7f), "%s", text[2].c_str()); - } - ImGui::EndChild(); - ImGui::EndTable(); + if (text[i].empty()) + continue; + const ImFont *font = i == 0 ? largeFont : regularFont; + textSize[i] = font->CalcTextSizeA(font->FontSize, FLT_MAX, maxW, text[i].c_str()); + totalSize.x = std::max(totalSize.x, textSize[i].x); + totalSize.y += textSize[i].y; + } + float topMargin = 0.f; + // image / left padding + if (imgSize.x != 0.f) + { + if (totalSize.y < imgSize.y) + topMargin = (imgSize.y - totalSize.y) / 2.f; + totalSize.x += imgSize.x + hspacing; + totalSize.y = std::max(totalSize.y, imgSize.y); + } + else { + totalSize.x += padding.x; + } + // right padding + totalSize.x += padding.x; + // border + totalSize += ImVec2(2.f, 2.f); + // draw background, border + ImVec2 pos(insetLeft, ImGui::GetIO().DisplaySize.y - totalSize.y * (1.f - animY)); + dl->AddRectFilled(pos, pos + totalSize, bg_col, 0.f); + dl->AddRect(pos, pos + totalSize, borderCol, 0.f); + + // draw image and text + pos += ImVec2(1.f, 1.f); // border + if (imgSize.x != 0.f) { + image.draw(dl, pos, imgSize, alpha); + pos.x += imgSize.x + hspacing; + } + else { + pos.x += padding.x; + } + pos.y += topMargin; + for (size_t i = 0; i < std::size(text); i++) + { + if (text[i].empty()) + continue; + const ImFont *font = i == 0 ? largeFont : regularFont; + const ImU32 col = alphaOverride(i == 0 ? 0xffffff : 0x00ffff, alpha); + dl->AddText(font, font->FontSize, pos, col, &text[i].front(), &text[i].back() + 1, maxW); + pos.y += textSize[i].y + vspacing; } - ImGui::End(); } - ImGui::GetStyle().Alpha = 1.f; return true; } diff --git a/core/rend/gui_achievements.h b/core/rend/gui_achievements.h index 733cc1a6c..30aa7c188 100644 --- a/core/rend/gui_achievements.h +++ b/core/rend/gui_achievements.h @@ -21,6 +21,7 @@ #include "gui_util.h" #include #include +#include namespace achievements { @@ -35,13 +36,16 @@ public: Unlocked, Progress, Mastery, - Challenge, + Challenge, // internal use + Leaderboard, // internal use Error }; void notify(Type type, const std::string& image, const std::string& text1, const std::string& text2 = {}, const std::string& text3 = {}); void showChallenge(const std::string& image); void hideChallenge(const std::string& image); + void showLeaderboard(u32 id, const std::string& text); + void hideLeaderboard(u32 id); bool draw(); private: @@ -52,6 +56,7 @@ private: std::string text[3]; std::mutex mutex; std::vector challenges; + std::map leaderboards; }; extern Notification notifier; diff --git a/core/rend/gui_util.cpp b/core/rend/gui_util.cpp index faf4316d7..8621fb231 100644 --- a/core/rend/gui_util.cpp +++ b/core/rend/gui_util.cpp @@ -36,6 +36,7 @@ static std::vector folderFiles; bool subfolders_read; extern int insetLeft, insetRight, insetTop, insetBottom; +extern ImFont *largeFont; void error_popup(); namespace hostfs @@ -728,6 +729,19 @@ void ImguiTexture::draw(const ImVec2& size, const ImVec4& tint_col, const ImVec4 } } +void ImguiTexture::draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& size, float alpha) const +{ + ImTextureID id = getId(); + if (id == ImTextureID{}) + return; + float ar = imguiDriver->getAspectRatio(id); + ImVec2 uv0, uv1; + setUV(ar, uv0, uv1); + ImVec2 pos_b = pos + size; + u32 col = alphaOverride(0xffffff, alpha); + drawList->AddImage(id, pos, pos_b, uv0, uv1, col); +} + bool ImguiTexture::button(const char* str_id, const ImVec2& image_size, const std::string& title, const ImVec4& bg_col, const ImVec4& tint_col) const { @@ -813,19 +827,19 @@ bool Toast::draw() // Fade out alpha = (std::cos((now - endTime) / (float)END_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; - extern ImFont *largeFont; // FIXME ImFont *regularFont = ImGui::GetFont(); const float maxW = uiScaled(640.f); const ImVec2 titleSize = title.empty() ? ImVec2() : largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, maxW, &title.front(), &title.back() + 1); const ImVec2 msgSize = message.empty() ? ImVec2() : regularFont->CalcTextSizeA(regularFont->FontSize, FLT_MAX, maxW, &message.front(), &message.back() + 1); - const ScaledVec2 padding(10.f, 10.f); - const ScaledVec2 spacing(0.f, 5.f); - const ImVec2 totalSize = titleSize + spacing + msgSize + padding * 2.f; + const ScaledVec2 padding(5.f, 4.f); + const ScaledVec2 spacing(0.f, 2.f); + ImVec2 totalSize(std::max(titleSize.x, msgSize.x), titleSize.y + msgSize.y); + totalSize += padding * 2.f + spacing * (float)(!title.empty() && !message.empty()); const ImVec2 displaySize(ImGui::GetIO().DisplaySize); - ImVec2 pos(0.f, displaySize.y - totalSize.y); + ImVec2 pos(insetLeft, displaySize.y - totalSize.y); if (now - startTime < START_ANIM_TIME) // Slide up pos.y += totalSize.y * (std::cos((now - startTime) / (float)START_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; @@ -840,7 +854,7 @@ bool Toast::draw() { const ImU32 col = alphaOverride(ImGui::GetColorU32(ImGuiCol_Text), alpha); dl->AddText(largeFont, largeFont->FontSize, pos, col, &title.front(), &title.back() + 1, maxW); - pos += spacing + ImVec2(0.f, titleSize.y); + pos.y += spacing.y + titleSize.y; } if (!message.empty()) { diff --git a/core/rend/gui_util.h b/core/rend/gui_util.h index 26e7ad3fb..c52f1956c 100644 --- a/core/rend/gui_util.h +++ b/core/rend/gui_util.h @@ -204,6 +204,7 @@ public: void draw(const ImVec2& size, const ImVec4& tint_col = ImVec4(1, 1, 1, 1), const ImVec4& border_col = ImVec4(0, 0, 0, 0)) const; + void draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& size, float alpha = 1.f) const; bool button(const char* str_id, const ImVec2& image_size, const std::string& title = {}, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), const ImVec4& tint_col = ImVec4(1, 1, 1, 1)) const; diff --git a/shell/libretro/libretro.cpp b/shell/libretro/libretro.cpp index a5af48220..4348b6748 100644 --- a/shell/libretro/libretro.cpp +++ b/shell/libretro/libretro.cpp @@ -196,7 +196,7 @@ static retro_rumble_interface rumble; static void refresh_devices(bool first_startup); static void init_disk_control_interface(); static bool read_m3u(const char *file); -void gui_display_notification(const char *msg, int duration); +void gui_display_notification(const char *msg, int duration, const char *details = nullptr); static void updateVibration(u32 port, float power, float inclination, u32 durationMs); static std::string game_data; @@ -3702,7 +3702,7 @@ static bool read_m3u(const char *file) return disk_index != 0; } -void gui_display_notification(const char *msg, int duration) +void gui_display_notification(const char *msg, int duration, const char *details) { retro_message retromsg; retromsg.msg = msg; From 13302b87c835c15c63519477ca270d17e1b0ee57 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 7 May 2024 22:23:51 +0200 Subject: [PATCH 65/86] move ui to its own folder --- CMakeLists.txt | 42 +++++++++---------- core/achievements/achievements.cpp | 2 +- core/emulator.cpp | 2 +- core/hw/maple/maple_devs.cpp | 2 +- core/hw/naomi/card_reader.cpp | 2 +- core/hw/naomi/naomi.cpp | 2 +- core/hw/naomi/naomi_m3comm.cpp | 2 +- core/hw/naomi/printer.cpp | 2 +- core/input/gamepad_device.cpp | 2 +- core/input/keyboard_device.h | 2 +- core/input/mouse.cpp | 2 +- core/linux-dist/main.cpp | 2 +- core/linux-dist/x11.cpp | 2 +- core/lua/lua.cpp | 4 +- core/network/ggpo.cpp | 4 +- core/network/naomi_network.cpp | 2 +- core/nullDC.cpp | 4 +- core/rend/dx11/dx11_driver.h | 4 +- core/rend/dx11/dx11_renderer.cpp | 2 +- core/rend/dx9/d3d_renderer.cpp | 2 +- core/rend/dx9/d3d_renderer.h | 2 +- core/rend/dx9/dx9_driver.h | 2 +- core/rend/gles/gles.cpp | 2 +- core/rend/gles/gles.h | 2 +- core/rend/gles/opengl_driver.cpp | 4 +- core/rend/gles/opengl_driver.h | 2 +- core/rend/vulkan/vulkan_context.cpp | 2 +- core/rend/vulkan/vulkan_driver.h | 2 +- core/rend/vulkan/vulkan_renderer.h | 2 +- core/{rend => ui}/IconsFontAwesome6.h | 0 core/{rend => ui}/boxart/boxart.cpp | 0 core/{rend => ui}/boxart/boxart.h | 0 core/{rend => ui}/boxart/gamesdb.cpp | 0 core/{rend => ui}/boxart/gamesdb.h | 0 core/{rend => ui}/boxart/pvrparser.h | 0 core/{rend => ui}/boxart/scraper.cpp | 0 core/{rend => ui}/boxart/scraper.h | 0 core/{rend => ui}/discord.cpp | 0 core/{rend => ui}/game_scanner.h | 0 core/{rend => ui}/gui.cpp | 4 +- core/{rend => ui}/gui.h | 0 core/{rend => ui}/gui_achievements.cpp | 0 core/{rend => ui}/gui_achievements.h | 0 core/{rend => ui}/gui_android.cpp | 0 core/{rend => ui}/gui_android.h | 0 core/{rend => ui}/gui_chat.h | 0 core/{rend => ui}/gui_cheats.cpp | 0 core/{rend => ui}/gui_util.cpp | 0 core/{rend => ui}/gui_util.h | 0 core/{rend => ui}/imgui_driver.cpp | 2 +- core/{rend => ui}/imgui_driver.h | 0 core/{rend => ui}/mainui.cpp | 0 core/{rend => ui}/mainui.h | 0 core/windows/winmain.cpp | 4 +- core/wsi/sdl.cpp | 2 +- .../flycast/src/main/jni/src/Android.cpp | 4 +- .../emulator-ios/emulator/AppDelegate.mm | 2 +- .../emulator-ios/emulator/EmulatorView.mm | 2 +- .../emulator/FlycastViewController.mm | 2 +- .../apple/emulator-ios/emulator/ios_gamepad.h | 2 +- .../emulator-osx/SDLApplicationDelegate.mm | 2 +- .../emulator-osx/emulator-osx/osx-main.mm | 2 +- shell/switch/switch_main.cpp | 2 +- 63 files changed, 69 insertions(+), 69 deletions(-) rename core/{rend => ui}/IconsFontAwesome6.h (100%) rename core/{rend => ui}/boxart/boxart.cpp (100%) rename core/{rend => ui}/boxart/boxart.h (100%) rename core/{rend => ui}/boxart/gamesdb.cpp (100%) rename core/{rend => ui}/boxart/gamesdb.h (100%) rename core/{rend => ui}/boxart/pvrparser.h (100%) rename core/{rend => ui}/boxart/scraper.cpp (100%) rename core/{rend => ui}/boxart/scraper.h (100%) rename core/{rend => ui}/discord.cpp (100%) rename core/{rend => ui}/game_scanner.h (100%) rename core/{rend => ui}/gui.cpp (99%) rename core/{rend => ui}/gui.h (100%) rename core/{rend => ui}/gui_achievements.cpp (100%) rename core/{rend => ui}/gui_achievements.h (100%) rename core/{rend => ui}/gui_android.cpp (100%) rename core/{rend => ui}/gui_android.h (100%) rename core/{rend => ui}/gui_chat.h (100%) rename core/{rend => ui}/gui_cheats.cpp (100%) rename core/{rend => ui}/gui_util.cpp (100%) rename core/{rend => ui}/gui_util.h (100%) rename core/{rend => ui}/imgui_driver.cpp (99%) rename core/{rend => ui}/imgui_driver.h (100%) rename core/{rend => ui}/mainui.cpp (100%) rename core/{rend => ui}/mainui.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 263d0b776..37cb26dd5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -734,7 +734,7 @@ if(USE_DISCORD AND NOT LIBRETRO) target_include_directories(${PROJECT_NAME} PRIVATE core/deps/discord-rpc/include) target_link_libraries(${PROJECT_NAME} PRIVATE discord-rpc) target_compile_definitions(${PROJECT_NAME} PRIVATE USE_DISCORD) - target_sources(${PROJECT_NAME} PRIVATE core/rend/discord.cpp) + target_sources(${PROJECT_NAME} PRIVATE core/ui/discord.cpp) endif() cmrc_add_resource_library(flycast-resources ALIAS flycast::res NAMESPACE flycast) @@ -1292,26 +1292,26 @@ target_sources(${PROJECT_NAME} PRIVATE core/rend/norend/norend.cpp) if(NOT LIBRETRO) target_sources(${PROJECT_NAME} PRIVATE - core/rend/game_scanner.h - core/rend/imgui_driver.cpp - core/rend/imgui_driver.h - core/rend/gui.cpp - core/rend/gui.h - core/rend/gui_achievements.cpp - core/rend/gui_android.cpp - core/rend/gui_android.h - core/rend/gui_chat.h - core/rend/gui_cheats.cpp - core/rend/gui_util.cpp - core/rend/gui_util.h - core/rend/mainui.cpp - core/rend/mainui.h - core/rend/boxart/boxart.cpp - core/rend/boxart/boxart.h - core/rend/boxart/gamesdb.cpp - core/rend/boxart/gamesdb.h - core/rend/boxart/scraper.cpp - core/rend/boxart/scraper.h) + core/ui/game_scanner.h + core/ui/imgui_driver.cpp + core/ui/imgui_driver.h + core/ui/gui.cpp + core/ui/gui.h + core/ui/gui_achievements.cpp + core/ui/gui_android.cpp + core/ui/gui_android.h + core/ui/gui_chat.h + core/ui/gui_cheats.cpp + core/ui/gui_util.cpp + core/ui/gui_util.h + core/ui/mainui.cpp + core/ui/mainui.h + core/ui/boxart/boxart.cpp + core/ui/boxart/boxart.h + core/ui/boxart/gamesdb.cpp + core/ui/boxart/gamesdb.h + core/ui/boxart/scraper.cpp + core/ui/boxart/scraper.h) endif() if(USE_VULKAN) diff --git a/core/achievements/achievements.cpp b/core/achievements/achievements.cpp index e5db54f7c..264bdf81a 100644 --- a/core/achievements/achievements.cpp +++ b/core/achievements/achievements.cpp @@ -23,7 +23,7 @@ #include "oslib/directory.h" #include "oslib/http_client.h" #include "hw/sh4/sh4_mem.h" -#include "rend/gui_achievements.h" +#include "ui/gui_achievements.h" #include "imgread/common.h" #include "cfg/option.h" #include "oslib/oslib.h" diff --git a/core/emulator.cpp b/core/emulator.cpp index acb7c9b10..9af7f6f5b 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -37,7 +37,7 @@ #include "network/ggpo.h" #include "hw/mem/mem_watch.h" #include "network/net_handshake.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "network/naomi_network.h" #include "serialize.h" #include "hw/pvr/pvr.h" diff --git a/core/hw/maple/maple_devs.cpp b/core/hw/maple/maple_devs.cpp index 7b63d615e..a0bba1b55 100755 --- a/core/hw/maple/maple_devs.cpp +++ b/core/hw/maple/maple_devs.cpp @@ -7,7 +7,7 @@ #include "oslib/oslib.h" #include "hw/aica/sgc_if.h" #include "cfg/option.h" -#include "rend/gui.h" +#include "ui/gui.h" #include #include #include diff --git a/core/hw/naomi/card_reader.cpp b/core/hw/naomi/card_reader.cpp index b914c39a7..0877dee27 100644 --- a/core/hw/naomi/card_reader.cpp +++ b/core/hw/naomi/card_reader.cpp @@ -21,7 +21,7 @@ #include "hw/sh4/modules/modules.h" #include "hw/maple/maple_cfg.h" #include "hw/maple/maple_devs.h" -#include "rend/gui.h" +#include "ui/gui.h" #include #include #include diff --git a/core/hw/naomi/naomi.cpp b/core/hw/naomi/naomi.cpp index 36b705eb0..6c4bbafd4 100644 --- a/core/hw/naomi/naomi.cpp +++ b/core/hw/naomi/naomi.cpp @@ -30,7 +30,7 @@ #include "serialize.h" #include "network/output.h" #include "hw/sh4/modules/modules.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "printer.h" #include "hw/flashrom/x76f100.h" diff --git a/core/hw/naomi/naomi_m3comm.cpp b/core/hw/naomi/naomi_m3comm.cpp index cd53bdb92..b6027238a 100644 --- a/core/hw/naomi/naomi_m3comm.cpp +++ b/core/hw/naomi/naomi_m3comm.cpp @@ -30,7 +30,7 @@ #include "hw/sh4/sh4_mem.h" #include "network/naomi_network.h" #include "emulator.h" -#include "rend/gui.h" +#include "ui/gui.h" #include #include diff --git a/core/hw/naomi/printer.cpp b/core/hw/naomi/printer.cpp index 29eb0db81..ffb8e492b 100644 --- a/core/hw/naomi/printer.cpp +++ b/core/hw/naomi/printer.cpp @@ -20,7 +20,7 @@ #include "stdclass.h" #include "printer.h" #include "serialize.h" -#include "rend/gui.h" +#include "ui/gui.h" #include #include #include diff --git a/core/input/gamepad_device.cpp b/core/input/gamepad_device.cpp index 702fcbc65..610ef5b85 100644 --- a/core/input/gamepad_device.cpp +++ b/core/input/gamepad_device.cpp @@ -20,7 +20,7 @@ #include "gamepad_device.h" #include "cfg/cfg.h" #include "stdclass.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "emulator.h" #include "hw/maple/maple_devs.h" #include "mouse.h" diff --git a/core/input/keyboard_device.h b/core/input/keyboard_device.h index c6cbb5d29..1ae17ce35 100644 --- a/core/input/keyboard_device.h +++ b/core/input/keyboard_device.h @@ -20,7 +20,7 @@ #include "types.h" #include "cfg/option.h" #include "gamepad_device.h" -#include "rend/gui.h" +#include "ui/gui.h" #include extern u8 kb_key[4][6]; // normal keys pressed diff --git a/core/input/mouse.cpp b/core/input/mouse.cpp index 6e171e512..7efdf933f 100644 --- a/core/input/mouse.cpp +++ b/core/input/mouse.cpp @@ -18,7 +18,7 @@ */ #include "mouse.h" #include "cfg/option.h" -#include "rend/gui.h" +#include "ui/gui.h" // Mouse buttons // bit 0: Button C diff --git a/core/linux-dist/main.cpp b/core/linux-dist/main.cpp index 26f19aaf1..83a18bc76 100644 --- a/core/linux-dist/main.cpp +++ b/core/linux-dist/main.cpp @@ -6,7 +6,7 @@ #if defined(__unix__) #include "log/LogManager.h" #include "emulator.h" -#include "rend/mainui.h" +#include "ui/mainui.h" #include "oslib/directory.h" #include "oslib/oslib.h" #include "stdclass.h" diff --git a/core/linux-dist/x11.cpp b/core/linux-dist/x11.cpp index a11d20a54..f7e3c329f 100644 --- a/core/linux-dist/x11.cpp +++ b/core/linux-dist/x11.cpp @@ -6,7 +6,7 @@ #include "types.h" #include "cfg/cfg.h" #include "x11.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "input/gamepad.h" #include "input/mouse.h" #include "icon.h" diff --git a/core/lua/lua.cpp b/core/lua/lua.cpp index ffd899799..96e928c3c 100644 --- a/core/lua/lua.cpp +++ b/core/lua/lua.cpp @@ -21,8 +21,8 @@ #ifdef USE_LUA #include #include -#include "rend/gui.h" -#include "rend/gui_util.h" +#include "ui/gui.h" +#include "ui/gui_util.h" #include "hw/mem/addrspace.h" #include "cfg/option.h" #include "emulator.h" diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp index 785d3bfbb..00363a5e8 100644 --- a/core/network/ggpo.cpp +++ b/core/network/ggpo.cpp @@ -72,8 +72,8 @@ static void getLocalInput(MapleInputState inputState[4]) #ifdef USE_GGPO #include "ggponet.h" #include "emulator.h" -#include "rend/gui.h" -#include "rend/gui_util.h" +#include "ui/gui.h" +#include "ui/gui_util.h" #include "hw/mem/mem_watch.h" #include #include diff --git a/core/network/naomi_network.cpp b/core/network/naomi_network.cpp index 758cea437..16170cd53 100644 --- a/core/network/naomi_network.cpp +++ b/core/network/naomi_network.cpp @@ -19,7 +19,7 @@ #include "naomi_network.h" #include "hw/naomi/naomi_flashrom.h" #include "cfg/option.h" -#include "rend/gui.h" +#include "ui/gui.h" #include #include diff --git a/core/nullDC.cpp b/core/nullDC.cpp index 0d69d71a1..be8955bf2 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -5,12 +5,12 @@ #include "cfg/cfg.h" #include "cfg/option.h" #include "log/LogManager.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "oslib/oslib.h" #include "oslib/directory.h" #include "debug/gdb_server.h" #include "archive/rzip.h" -#include "rend/mainui.h" +#include "ui/mainui.h" #include "input/gamepad_device.h" #include "lua/lua.h" #include "stdclass.h" diff --git a/core/rend/dx11/dx11_driver.h b/core/rend/dx11/dx11_driver.h index a10c59f09..5807c2f01 100644 --- a/core/rend/dx11/dx11_driver.h +++ b/core/rend/dx11/dx11_driver.h @@ -17,10 +17,10 @@ along with Flycast. If not, see . */ #pragma once -#include "rend/imgui_driver.h" +#include "ui/imgui_driver.h" #include "imgui_impl_dx11.h" #include "dx11context.h" -#include "rend/gui.h" +#include "ui/gui.h" #include class DX11Driver final : public ImGuiDriver diff --git a/core/rend/dx11/dx11_renderer.cpp b/core/rend/dx11/dx11_renderer.cpp index 4a2d79765..b52e8fd3f 100644 --- a/core/rend/dx11/dx11_renderer.cpp +++ b/core/rend/dx11/dx11_renderer.cpp @@ -20,7 +20,7 @@ #include "dx11context.h" #include "hw/pvr/ta.h" #include "hw/pvr/pvr_mem.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "rend/tileclip.h" #include "rend/sorter.h" diff --git a/core/rend/dx9/d3d_renderer.cpp b/core/rend/dx9/d3d_renderer.cpp index 8c9b758be..42d892395 100644 --- a/core/rend/dx9/d3d_renderer.cpp +++ b/core/rend/dx9/d3d_renderer.cpp @@ -20,7 +20,7 @@ #include "hw/pvr/ta.h" #include "hw/pvr/pvr_mem.h" #include "rend/tileclip.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "rend/sorter.h" const u32 DstBlendGL[] diff --git a/core/rend/dx9/d3d_renderer.h b/core/rend/dx9/d3d_renderer.h index 36e360cd1..07787564c 100644 --- a/core/rend/dx9/d3d_renderer.h +++ b/core/rend/dx9/d3d_renderer.h @@ -25,7 +25,7 @@ #include "rend/transform_matrix.h" #include "d3d_texture.h" #include "d3d_shaders.h" -#include "rend/imgui_driver.h" +#include "ui/imgui_driver.h" class RenderStateCache { diff --git a/core/rend/dx9/dx9_driver.h b/core/rend/dx9/dx9_driver.h index a6ceb6d6b..e1fe7527d 100644 --- a/core/rend/dx9/dx9_driver.h +++ b/core/rend/dx9/dx9_driver.h @@ -17,7 +17,7 @@ along with Flycast. If not, see . */ #pragma once -#include "rend/imgui_driver.h" +#include "ui/imgui_driver.h" #include "imgui_impl_dx9.h" #include "dxcontext.h" #include diff --git a/core/rend/gles/gles.cpp b/core/rend/gles/gles.cpp index 213ed62dd..54117246c 100644 --- a/core/rend/gles/gles.cpp +++ b/core/rend/gles/gles.cpp @@ -2,7 +2,7 @@ #include "gles.h" #include "hw/pvr/ta.h" #ifndef LIBRETRO -#include "rend/gui.h" +#include "ui/gui.h" #else #include "rend/gles/postprocess.h" #include "vmu_xhair.h" diff --git a/core/rend/gles/gles.h b/core/rend/gles/gles.h index 9f3f0c076..3063bd341 100755 --- a/core/rend/gles/gles.h +++ b/core/rend/gles/gles.h @@ -7,7 +7,7 @@ #include "glcache.h" #include "rend/shader_util.h" #ifndef LIBRETRO -#include "rend/imgui_driver.h" +#include "ui/imgui_driver.h" #endif #include diff --git a/core/rend/gles/opengl_driver.cpp b/core/rend/gles/opengl_driver.cpp index 60663cfb9..3d63a2570 100644 --- a/core/rend/gles/opengl_driver.cpp +++ b/core/rend/gles/opengl_driver.cpp @@ -20,8 +20,8 @@ #include "imgui_impl_opengl3.h" #include "wsi/gl_context.h" #include "rend/osd.h" -#include "rend/gui.h" -#include "rend/gui_util.h" +#include "ui/gui.h" +#include "ui/gui_util.h" #include "glcache.h" #include "gles.h" diff --git a/core/rend/gles/opengl_driver.h b/core/rend/gles/opengl_driver.h index c100e6c95..59c41d146 100644 --- a/core/rend/gles/opengl_driver.h +++ b/core/rend/gles/opengl_driver.h @@ -17,7 +17,7 @@ along with Flycast. If not, see . */ #pragma once -#include "rend/imgui_driver.h" +#include "ui/imgui_driver.h" #include "emulator.h" #include diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index 846cc4c2a..e58252c71 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -22,7 +22,7 @@ #include "vulkan_renderer.h" #include "imgui.h" #include "imgui_impl_vulkan.h" -#include "../gui.h" +#include "ui/gui.h" #ifdef USE_SDL #include #include diff --git a/core/rend/vulkan/vulkan_driver.h b/core/rend/vulkan/vulkan_driver.h index 12c62ac34..0a8bc30cc 100644 --- a/core/rend/vulkan/vulkan_driver.h +++ b/core/rend/vulkan/vulkan_driver.h @@ -17,7 +17,7 @@ along with Flycast. If not, see . */ #pragma once -#include "rend/imgui_driver.h" +#include "ui/imgui_driver.h" #include "imgui_impl_vulkan.h" #include "vulkan_context.h" #include "texture.h" diff --git a/core/rend/vulkan/vulkan_renderer.h b/core/rend/vulkan/vulkan_renderer.h index 91a319082..f31b2328f 100644 --- a/core/rend/vulkan/vulkan_renderer.h +++ b/core/rend/vulkan/vulkan_renderer.h @@ -25,7 +25,7 @@ #include "rend/osd.h" #include "rend/transform_matrix.h" #ifndef LIBRETRO -#include "rend/gui.h" +#include "ui/gui.h" #endif #include diff --git a/core/rend/IconsFontAwesome6.h b/core/ui/IconsFontAwesome6.h similarity index 100% rename from core/rend/IconsFontAwesome6.h rename to core/ui/IconsFontAwesome6.h diff --git a/core/rend/boxart/boxart.cpp b/core/ui/boxart/boxart.cpp similarity index 100% rename from core/rend/boxart/boxart.cpp rename to core/ui/boxart/boxart.cpp diff --git a/core/rend/boxart/boxart.h b/core/ui/boxart/boxart.h similarity index 100% rename from core/rend/boxart/boxart.h rename to core/ui/boxart/boxart.h diff --git a/core/rend/boxart/gamesdb.cpp b/core/ui/boxart/gamesdb.cpp similarity index 100% rename from core/rend/boxart/gamesdb.cpp rename to core/ui/boxart/gamesdb.cpp diff --git a/core/rend/boxart/gamesdb.h b/core/ui/boxart/gamesdb.h similarity index 100% rename from core/rend/boxart/gamesdb.h rename to core/ui/boxart/gamesdb.h diff --git a/core/rend/boxart/pvrparser.h b/core/ui/boxart/pvrparser.h similarity index 100% rename from core/rend/boxart/pvrparser.h rename to core/ui/boxart/pvrparser.h diff --git a/core/rend/boxart/scraper.cpp b/core/ui/boxart/scraper.cpp similarity index 100% rename from core/rend/boxart/scraper.cpp rename to core/ui/boxart/scraper.cpp diff --git a/core/rend/boxart/scraper.h b/core/ui/boxart/scraper.h similarity index 100% rename from core/rend/boxart/scraper.h rename to core/ui/boxart/scraper.h diff --git a/core/rend/discord.cpp b/core/ui/discord.cpp similarity index 100% rename from core/rend/discord.cpp rename to core/ui/discord.cpp diff --git a/core/rend/game_scanner.h b/core/ui/game_scanner.h similarity index 100% rename from core/rend/game_scanner.h rename to core/ui/game_scanner.h diff --git a/core/rend/gui.cpp b/core/ui/gui.cpp similarity index 99% rename from core/rend/gui.cpp rename to core/ui/gui.cpp index 424c7ff2e..8efddf932 100644 --- a/core/rend/gui.cpp +++ b/core/ui/gui.cpp @@ -17,7 +17,7 @@ along with Flycast. If not, see . */ #include "gui.h" -#include "osd.h" +#include "rend/osd.h" #include "cfg/cfg.h" #include "hw/maple/maple_if.h" #include "hw/maple/maple_devs.h" @@ -35,7 +35,7 @@ #include "imgread/common.h" #include "log/LogManager.h" #include "emulator.h" -#include "rend/mainui.h" +#include "mainui.h" #include "lua/lua.h" #include "gui_chat.h" #include "imgui_driver.h" diff --git a/core/rend/gui.h b/core/ui/gui.h similarity index 100% rename from core/rend/gui.h rename to core/ui/gui.h diff --git a/core/rend/gui_achievements.cpp b/core/ui/gui_achievements.cpp similarity index 100% rename from core/rend/gui_achievements.cpp rename to core/ui/gui_achievements.cpp diff --git a/core/rend/gui_achievements.h b/core/ui/gui_achievements.h similarity index 100% rename from core/rend/gui_achievements.h rename to core/ui/gui_achievements.h diff --git a/core/rend/gui_android.cpp b/core/ui/gui_android.cpp similarity index 100% rename from core/rend/gui_android.cpp rename to core/ui/gui_android.cpp diff --git a/core/rend/gui_android.h b/core/ui/gui_android.h similarity index 100% rename from core/rend/gui_android.h rename to core/ui/gui_android.h diff --git a/core/rend/gui_chat.h b/core/ui/gui_chat.h similarity index 100% rename from core/rend/gui_chat.h rename to core/ui/gui_chat.h diff --git a/core/rend/gui_cheats.cpp b/core/ui/gui_cheats.cpp similarity index 100% rename from core/rend/gui_cheats.cpp rename to core/ui/gui_cheats.cpp diff --git a/core/rend/gui_util.cpp b/core/ui/gui_util.cpp similarity index 100% rename from core/rend/gui_util.cpp rename to core/ui/gui_util.cpp diff --git a/core/rend/gui_util.h b/core/ui/gui_util.h similarity index 100% rename from core/rend/gui_util.h rename to core/ui/gui_util.h diff --git a/core/rend/imgui_driver.cpp b/core/ui/imgui_driver.cpp similarity index 99% rename from core/rend/imgui_driver.cpp rename to core/ui/imgui_driver.cpp index 91d81fbf2..e6b36b3f4 100644 --- a/core/rend/imgui_driver.cpp +++ b/core/ui/imgui_driver.cpp @@ -18,7 +18,7 @@ */ #include "imgui_driver.h" #include "gui_util.h" -#include "osd.h" +#include "rend/osd.h" #define STBI_ONLY_JPEG #define STBI_ONLY_PNG #include diff --git a/core/rend/imgui_driver.h b/core/ui/imgui_driver.h similarity index 100% rename from core/rend/imgui_driver.h rename to core/ui/imgui_driver.h diff --git a/core/rend/mainui.cpp b/core/ui/mainui.cpp similarity index 100% rename from core/rend/mainui.cpp rename to core/ui/mainui.cpp diff --git a/core/rend/mainui.h b/core/ui/mainui.h similarity index 100% rename from core/rend/mainui.h rename to core/ui/mainui.h diff --git a/core/windows/winmain.cpp b/core/windows/winmain.cpp index 9146916f5..a49446d47 100644 --- a/core/windows/winmain.cpp +++ b/core/windows/winmain.cpp @@ -27,7 +27,7 @@ #include #include #include "cfg/option.h" -#include "rend/gui.h" +#include "ui/gui.h" #endif #include "oslib/oslib.h" #include "stdclass.h" @@ -35,7 +35,7 @@ #include "log/LogManager.h" #include "sdl/sdl.h" #include "emulator.h" -#include "rend/mainui.h" +#include "ui/mainui.h" #include "oslib/directory.h" #ifdef USE_BREAKPAD #include "breakpad/client/windows/handler/exception_handler.h" diff --git a/core/wsi/sdl.cpp b/core/wsi/sdl.cpp index e187a8e6a..dddc940a5 100644 --- a/core/wsi/sdl.cpp +++ b/core/wsi/sdl.cpp @@ -20,7 +20,7 @@ */ #if defined(USE_SDL) #include "gl_context.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "sdl/sdl.h" #include "cfg/option.h" diff --git a/shell/android-studio/flycast/src/main/jni/src/Android.cpp b/shell/android-studio/flycast/src/main/jni/src/Android.cpp index 351f6c564..553a83578 100644 --- a/shell/android-studio/flycast/src/main/jni/src/Android.cpp +++ b/shell/android-studio/flycast/src/main/jni/src/Android.cpp @@ -6,13 +6,13 @@ #include "hw/naomi/naomi_cart.h" #include "audio/audiostream.h" #include "imgread/common.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "rend/osd.h" #include "cfg/cfg.h" #include "log/LogManager.h" #include "wsi/context.h" #include "emulator.h" -#include "rend/mainui.h" +#include "ui/mainui.h" #include "cfg/option.h" #include "stdclass.h" #include "oslib/oslib.h" diff --git a/shell/apple/emulator-ios/emulator/AppDelegate.mm b/shell/apple/emulator-ios/emulator/AppDelegate.mm index dc5760173..592f5311e 100644 --- a/shell/apple/emulator-ios/emulator/AppDelegate.mm +++ b/shell/apple/emulator-ios/emulator/AppDelegate.mm @@ -28,7 +28,7 @@ #include "emulator.h" #include "log/LogManager.h" #include "cfg/option.h" -#include "rend/gui.h" +#include "ui/gui.h" static bool emulatorRunning; diff --git a/shell/apple/emulator-ios/emulator/EmulatorView.mm b/shell/apple/emulator-ios/emulator/EmulatorView.mm index e10491774..d77e2eba0 100644 --- a/shell/apple/emulator-ios/emulator/EmulatorView.mm +++ b/shell/apple/emulator-ios/emulator/EmulatorView.mm @@ -20,7 +20,7 @@ #import "EmulatorView.h" #include "types.h" -#include "rend/gui.h" +#include "ui/gui.h" #include "ios_gamepad.h" @implementation EmulatorView { diff --git a/shell/apple/emulator-ios/emulator/FlycastViewController.mm b/shell/apple/emulator-ios/emulator/FlycastViewController.mm index 018c261a9..fb437bead 100644 --- a/shell/apple/emulator-ios/emulator/FlycastViewController.mm +++ b/shell/apple/emulator-ios/emulator/FlycastViewController.mm @@ -33,7 +33,7 @@ #include "types.h" #include "wsi/context.h" -#include "rend/mainui.h" +#include "ui/mainui.h" #include "emulator.h" #include "log/LogManager.h" #include "stdclass.h" diff --git a/shell/apple/emulator-ios/emulator/ios_gamepad.h b/shell/apple/emulator-ios/emulator/ios_gamepad.h index 08159cc84..3d768c713 100644 --- a/shell/apple/emulator-ios/emulator/ios_gamepad.h +++ b/shell/apple/emulator-ios/emulator/ios_gamepad.h @@ -23,7 +23,7 @@ #include #include "input/gamepad_device.h" #include "input/mouse.h" -#include "rend/gui.h" +#include "ui/gui.h" enum IOSButton { IOS_BTN_A = 1, diff --git a/shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm b/shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm index 1ebbe7e9d..0b2dd71d0 100644 --- a/shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm +++ b/shell/apple/emulator-osx/emulator-osx/SDLApplicationDelegate.mm @@ -9,7 +9,7 @@ #include "emulator.h" #include /* for MAXPATHLEN */ #include -#include "rend/gui.h" +#include "ui/gui.h" #include "oslib/oslib.h" #ifdef USE_BREAKPAD diff --git a/shell/apple/emulator-osx/emulator-osx/osx-main.mm b/shell/apple/emulator-osx/emulator-osx/osx-main.mm index bf5eb401f..089ce81f4 100644 --- a/shell/apple/emulator-osx/emulator-osx/osx-main.mm +++ b/shell/apple/emulator-osx/emulator-osx/osx-main.mm @@ -21,7 +21,7 @@ #include "stdclass.h" #include "oslib/oslib.h" #include "emulator.h" -#include "rend/mainui.h" +#include "ui/mainui.h" #include int darw_printf(const char* text, ...) diff --git a/shell/switch/switch_main.cpp b/shell/switch/switch_main.cpp index 1c3fdcffe..ffc102655 100644 --- a/shell/switch/switch_main.cpp +++ b/shell/switch/switch_main.cpp @@ -19,7 +19,7 @@ #include "stdclass.h" #include "log/LogManager.h" #include "emulator.h" -#include "rend/mainui.h" +#include "ui/mainui.h" #include "oslib/directory.h" int main(int argc, char *argv[]) From c53a1c42c695384b0d02fa91309d345d5d2832f4 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 7 May 2024 22:33:46 +0200 Subject: [PATCH 66/86] work around mingw-w64 init crash commit https://sourceforge.net/p/mingw-w64/mingw-w64/ci/7b33798917a50cf023f1622eee8eba6d7874b796/ introduces a static initialization order fiasco if a static object is calling clock_gettime() in its constructor. --- CMakeLists.txt | 1 + core/windows/clock.c | 287 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+) create mode 100644 core/windows/clock.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 37cb26dd5..48b1cf325 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1780,6 +1780,7 @@ if(NOT LIBRETRO) elseif(WIN32) if(NOT BUILD_TESTING) target_sources(${PROJECT_NAME} PRIVATE + core/windows/clock.c core/windows/rawinput.cpp core/windows/rawinput.h core/windows/winmain.cpp) diff --git a/core/windows/clock.c b/core/windows/clock.c new file mode 100644 index 000000000..be5bc057c --- /dev/null +++ b/core/windows/clock.c @@ -0,0 +1,287 @@ +// FIXME mingw-w64 commit https://sourceforge.net/p/mingw-w64/mingw-w64/ci/7b33798917a50cf023f1622eee8eba6d7874b796/ +// A constructor is now run to determine which win32 api is used for the CLOCK_REALTIME clock. +// But if a static variable calls clock_gettime in its constructor, it may call into a null function pointer. +// This is what happens with the discord-rpc library so until this is fixed, we stick to the working version. +/** + * This file has no copyright assigned and is placed in the Public Domain. + * This file is part of the w64 mingw-runtime package. + * No warranty is given; refer to the file DISCLAIMER.PD within this package. + */ +#ifdef __GNUC__ +#include +#include +#include +#include +#ifndef IN_WINPTHREAD +#define IN_WINPTHREAD 1 +#endif +#include "pthread.h" +#include "pthread_time.h" + +#define POW10_7 10000000 +#define POW10_9 1000000000 + +/* Number of 100ns-seconds between the beginning of the Windows epoch + * (Jan. 1, 1601) and the Unix epoch (Jan. 1, 1970) + */ +#define DELTA_EPOCH_IN_100NS INT64_C(116444736000000000) + +static WINPTHREADS_INLINE int lc_set_errno(int result) +{ + if (result != 0) { + errno = result; + return -1; + } + return 0; +} + +typedef void (WINAPI * GetSystemTimeAsFileTime_t)(LPFILETIME); +static GetSystemTimeAsFileTime_t GetSystemTimeAsFileTime_p /* = 0 */; + +static GetSystemTimeAsFileTime_t try_load_GetSystemPreciseTimeAsFileTime(void) +{ + /* Use GetSystemTimePreciseAsFileTime() if available (Windows 8 or later) */ + HMODULE mod = GetModuleHandle("kernel32.dll"); + GetSystemTimeAsFileTime_t get_time = NULL; + if (mod) + get_time = (GetSystemTimeAsFileTime_t)(intptr_t)GetProcAddress(mod, + "GetSystemTimePreciseAsFileTime"); /* <1us precision on Windows 10 */ + if (get_time == NULL) + get_time = GetSystemTimeAsFileTime; /* >15ms precision on Windows 10 */ + __atomic_store_n(&GetSystemTimeAsFileTime_p, get_time, __ATOMIC_RELAXED); + return get_time; +} + +static WINPTHREADS_INLINE GetSystemTimeAsFileTime_t load_GetSystemTimeBestAsFileTime(void) +{ + GetSystemTimeAsFileTime_t get_time = + __atomic_load_n(&GetSystemTimeAsFileTime_p, __ATOMIC_RELAXED); + if (get_time == NULL) + get_time = try_load_GetSystemPreciseTimeAsFileTime(); + return get_time; +} + +/** + * Get the resolution of the specified clock clock_id and + * stores it in the struct timespec pointed to by res. + * @param clock_id The clock_id argument is the identifier of the particular + * clock on which to act. The following clocks are supported: + *

+ *     CLOCK_REALTIME  System-wide real-time clock. Setting this clock
+ *                 requires appropriate privileges.
+ *     CLOCK_MONOTONIC Clock that cannot be set and represents monotonic
+ *                 time since some unspecified starting point.
+ *     CLOCK_PROCESS_CPUTIME_ID High-resolution per-process timer from the CPU.
+ *     CLOCK_THREAD_CPUTIME_ID  Thread-specific CPU-time clock.
+ * 
+ * @param res The pointer to a timespec structure to receive the time + * resolution. + * @return If the function succeeds, the return value is 0. + * If the function fails, the return value is -1, + * with errno set to indicate the error. + */ +int clock_getres(clockid_t clock_id, struct timespec *res) +{ + clockid_t id = clock_id; + + if (id == CLOCK_REALTIME && load_GetSystemTimeBestAsFileTime() == GetSystemTimeAsFileTime) + id = CLOCK_REALTIME_COARSE; /* GetSystemTimePreciseAsFileTime() not available */ + + switch(id) { + case CLOCK_REALTIME: + case CLOCK_MONOTONIC: + { + LARGE_INTEGER pf; + + if (QueryPerformanceFrequency(&pf) == 0) + return lc_set_errno(EINVAL); + + res->tv_sec = 0; + res->tv_nsec = (int) ((POW10_9 + (pf.QuadPart >> 1)) / pf.QuadPart); + if (res->tv_nsec < 1) + res->tv_nsec = 1; + + return 0; + } + + case CLOCK_REALTIME_COARSE: + case CLOCK_PROCESS_CPUTIME_ID: + case CLOCK_THREAD_CPUTIME_ID: + { + DWORD timeAdjustment, timeIncrement; + BOOL isTimeAdjustmentDisabled; + + (void) GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, &isTimeAdjustmentDisabled); + res->tv_sec = 0; + res->tv_nsec = timeIncrement * 100; + + return 0; + } + default: + break; + } + + return lc_set_errno(EINVAL); +} + +/** + * Get the time of the specified clock clock_id and stores it in the struct + * timespec pointed to by tp. + * @param clock_id The clock_id argument is the identifier of the particular + * clock on which to act. The following clocks are supported: + *
+ *     CLOCK_REALTIME  System-wide real-time clock. Setting this clock
+ *                 requires appropriate privileges.
+ *     CLOCK_MONOTONIC Clock that cannot be set and represents monotonic
+ *                 time since some unspecified starting point.
+ *     CLOCK_PROCESS_CPUTIME_ID High-resolution per-process timer from the CPU.
+ *     CLOCK_THREAD_CPUTIME_ID  Thread-specific CPU-time clock.
+ * 
+ * @param tp The pointer to a timespec structure to receive the time. + * @return If the function succeeds, the return value is 0. + * If the function fails, the return value is -1, + * with errno set to indicate the error. + */ +int clock_gettime(clockid_t clock_id, struct timespec *tp) +{ + unsigned __int64 t; + LARGE_INTEGER pf, pc; + union { + unsigned __int64 u64; + FILETIME ft; + } ct, et, kt, ut; + + switch(clock_id) { + case CLOCK_REALTIME: + { + load_GetSystemTimeBestAsFileTime()(&ct.ft); + t = ct.u64 - DELTA_EPOCH_IN_100NS; + tp->tv_sec = t / POW10_7; + tp->tv_nsec = ((int) (t % POW10_7)) * 100; + + return 0; + } + + case CLOCK_REALTIME_COARSE: + { + GetSystemTimeAsFileTime(&ct.ft); + t = ct.u64 - DELTA_EPOCH_IN_100NS; + tp->tv_sec = t / POW10_7; + tp->tv_nsec = ((int) (t % POW10_7)) * 100; + + return 0; + } + + case CLOCK_MONOTONIC: + { + if (QueryPerformanceFrequency(&pf) == 0) + return lc_set_errno(EINVAL); + + if (QueryPerformanceCounter(&pc) == 0) + return lc_set_errno(EINVAL); + + tp->tv_sec = pc.QuadPart / pf.QuadPart; + tp->tv_nsec = (int) (((pc.QuadPart % pf.QuadPart) * POW10_9 + (pf.QuadPart >> 1)) / pf.QuadPart); + if (tp->tv_nsec >= POW10_9) { + tp->tv_sec ++; + tp->tv_nsec -= POW10_9; + } + + return 0; + } + + case CLOCK_PROCESS_CPUTIME_ID: + { + if(0 == GetProcessTimes(GetCurrentProcess(), &ct.ft, &et.ft, &kt.ft, &ut.ft)) + return lc_set_errno(EINVAL); + t = kt.u64 + ut.u64; + tp->tv_sec = t / POW10_7; + tp->tv_nsec = ((int) (t % POW10_7)) * 100; + + return 0; + } + + case CLOCK_THREAD_CPUTIME_ID: + { + if(0 == GetThreadTimes(GetCurrentThread(), &ct.ft, &et.ft, &kt.ft, &ut.ft)) + return lc_set_errno(EINVAL); + t = kt.u64 + ut.u64; + tp->tv_sec = t / POW10_7; + tp->tv_nsec = ((int) (t % POW10_7)) * 100; + + return 0; + } + + default: + break; + } + + return lc_set_errno(EINVAL); +} + +/** + * Sleep for the specified time. + * @param clock_id This argument should always be CLOCK_REALTIME (0). + * @param flags 0 for relative sleep interval, others for absolute waking up. + * @param request The desired sleep interval or absolute waking up time. + * @param remain The remain amount of time to sleep. + * The current implemention just ignore it. + * @return If the function succeeds, the return value is 0. + * If the function fails, the return value is -1, + * with errno set to indicate the error. + */ +int clock_nanosleep(clockid_t clock_id, int flags, + const struct timespec *request, + struct timespec *remain) +{ + struct timespec tp; + + if (clock_id != CLOCK_REALTIME) + return lc_set_errno(EINVAL); + + if (flags == 0) + return nanosleep(request, remain); + + /* TIMER_ABSTIME = 1 */ + clock_gettime(CLOCK_REALTIME, &tp); + + tp.tv_sec = request->tv_sec - tp.tv_sec; + tp.tv_nsec = request->tv_nsec - tp.tv_nsec; + if (tp.tv_nsec < 0) { + tp.tv_nsec += POW10_9; + tp.tv_sec --; + } + + return nanosleep(&tp, remain); +} + +/** + * Set the time of the specified clock clock_id. + * @param clock_id This argument should always be CLOCK_REALTIME (0). + * @param tp The requested time. + * @return If the function succeeds, the return value is 0. + * If the function fails, the return value is -1, + * with errno set to indicate the error. + */ +int clock_settime(clockid_t clock_id, const struct timespec *tp) +{ + SYSTEMTIME st; + + union { + unsigned __int64 u64; + FILETIME ft; + } t; + + if (clock_id != CLOCK_REALTIME) + return lc_set_errno(EINVAL); + + t.u64 = tp->tv_sec * (__int64) POW10_7 + tp->tv_nsec / 100 + DELTA_EPOCH_IN_100NS; + if (FileTimeToSystemTime(&t.ft, &st) == 0) + return lc_set_errno(EINVAL); + + if (SetSystemTime(&st) == 0) + return lc_set_errno(EPERM); + + return 0; +} +#endif From 9bf60dde3c7438ba3ab5bfe121964d59834e0776 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Thu, 9 May 2024 10:21:56 +0200 Subject: [PATCH 67/86] ui: bug fixes. cheats window changes Fix boxart list navigation with DPad (regression). Get rid of several bogus PopStyleVar/PopID. Better navigation of pause menu buttons. imgui: tweak NavScoreItem to help pause menu nav. Allow achievement list navigation with DPad. Open cheats window fullscreen. Open Add Cheat window as a modal popup. Add game_scanner.cpp --- CMakeLists.txt | 11 +-- core/deps/imgui/imgui.cpp | 2 +- core/ui/boxart/boxart.cpp | 1 + core/ui/boxart/pvrparser.h | 2 +- core/ui/discord.cpp | 1 - core/ui/game_scanner.cpp | 161 +++++++++++++++++++++++++++++++++++ core/ui/game_scanner.h | 154 +++------------------------------ core/ui/gui.cpp | 12 +-- core/ui/gui_achievements.cpp | 3 +- core/ui/gui_achievements.h | 1 - core/ui/gui_cheats.cpp | 79 +++++++++-------- 11 files changed, 227 insertions(+), 200 deletions(-) create mode 100644 core/ui/game_scanner.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 48b1cf325..640375fe1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1292,6 +1292,7 @@ target_sources(${PROJECT_NAME} PRIVATE core/rend/norend/norend.cpp) if(NOT LIBRETRO) target_sources(${PROJECT_NAME} PRIVATE + core/ui/game_scanner.cpp core/ui/game_scanner.h core/ui/imgui_driver.cpp core/ui/imgui_driver.h @@ -1778,12 +1779,12 @@ if(NOT LIBRETRO) core/linux-dist/main.cpp) endif() elseif(WIN32) + target_sources(${PROJECT_NAME} PRIVATE + core/windows/clock.c + core/windows/rawinput.cpp + core/windows/rawinput.h) if(NOT BUILD_TESTING) - target_sources(${PROJECT_NAME} PRIVATE - core/windows/clock.c - core/windows/rawinput.cpp - core/windows/rawinput.h - core/windows/winmain.cpp) + target_sources(${PROJECT_NAME} PRIVATE core/windows/winmain.cpp) endif() if(WINDOWS_STORE) file(READ shell/uwp/Package.appxmanifest MANIFEST) diff --git a/core/deps/imgui/imgui.cpp b/core/deps/imgui/imgui.cpp index a309d6836..65fde5be2 100644 --- a/core/deps/imgui/imgui.cpp +++ b/core/deps/imgui/imgui.cpp @@ -11351,7 +11351,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) // Compute distance between boxes // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); - float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.1f), ImLerp(cand.Min.y, cand.Max.y, 0.9f), ImLerp(curr.Min.y, curr.Max.y, 0.1f), ImLerp(curr.Min.y, curr.Max.y, 0.9f)); // Scale down on Y to keep using box-distance for vertically touching items if (dby != 0.0f && dbx != 0.0f) dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); float dist_box = ImFabs(dbx) + ImFabs(dby); diff --git a/core/ui/boxart/boxart.cpp b/core/ui/boxart/boxart.cpp index b5a1ae59c..dc3f42c32 100644 --- a/core/ui/boxart/boxart.cpp +++ b/core/ui/boxart/boxart.cpp @@ -20,6 +20,7 @@ #include "gamesdb.h" #include "../game_scanner.h" #include "oslib/oslib.h" +#include "cfg/option.h" #include GameBoxart Boxart::getBoxart(const GameMedia& media) diff --git a/core/ui/boxart/pvrparser.h b/core/ui/boxart/pvrparser.h index 3cf699553..989f52e24 100644 --- a/core/ui/boxart/pvrparser.h +++ b/core/ui/boxart/pvrparser.h @@ -40,7 +40,7 @@ enum PvrDataFormat { PvrSquareTwiddledMipmapsAlt = 0x12, }; -static bool pvrParse(const u8 *data, u32 len, u32& width, u32& height, std::vector& out) +static inline bool pvrParse(const u8 *data, u32 len, u32& width, u32& height, std::vector& out) { if (len < 16) return false; diff --git a/core/ui/discord.cpp b/core/ui/discord.cpp index a447e311d..02ffd8dc1 100644 --- a/core/ui/discord.cpp +++ b/core/ui/discord.cpp @@ -18,7 +18,6 @@ */ #include "types.h" #include "emulator.h" -#include "stdclass.h" #include "cfg/option.h" #include "gui.h" #include "discord_rpc.h" diff --git a/core/ui/game_scanner.cpp b/core/ui/game_scanner.cpp new file mode 100644 index 000000000..49666726e --- /dev/null +++ b/core/ui/game_scanner.cpp @@ -0,0 +1,161 @@ +/* + Copyright 2024 flyinghead + + This file is part of Flycast. + + Flycast is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + Flycast is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Flycast. If not, see . +*/ +#include "game_scanner.h" +#include "stdclass.h" +#include "oslib/oslib.h" +#include "oslib/storage.h" +#include "cfg/option.h" + +static bool operator<(const GameMedia &left, const GameMedia &right) +{ + return left.name < right.name; +} + +void GameScanner::insert_game(const GameMedia& game) +{ + LockGuard _(mutex); + game_list.insert(std::upper_bound(game_list.begin(), game_list.end(), game), game); +} + +void GameScanner::insert_arcade_game(const GameMedia& game) +{ + arcade_game_list.insert(std::upper_bound(arcade_game_list.begin(), arcade_game_list.end(), game), game); +} + +void GameScanner::add_game_directory(const std::string& path) +{ + hostfs::DirectoryTree tree(path); + std::string emptyParentPath; + for (const hostfs::FileInfo& item : tree) + { + if (!running) + break; + + if (game_list.empty()) + { + // This won't work for android content uris + size_t slash = get_last_slash_pos(item.path); + std::string parentPath; + if (slash != 0 && slash != std::string::npos) + parentPath = item.path.substr(0, slash); + else + parentPath = item.path; + if (parentPath != emptyParentPath) + { + ++empty_folders_scanned; + emptyParentPath = parentPath; + if (empty_folders_scanned > 1000) + content_path_looks_incorrect = true; + } + } + else + { + content_path_looks_incorrect = false; + } + + if (item.name.substr(0, 2) == "._") + // Ignore Mac OS turds + continue; + std::string fileName(item.name); + std::string gameName(get_file_basename(item.name)); + std::string extension = get_file_extension(item.name); + if (extension == "zip" || extension == "7z") + { + string_tolower(gameName); + auto it = arcade_games.find(gameName); + if (it == arcade_games.end()) + continue; + gameName = it->second->description; + fileName = fileName + " (" + gameName + ")"; + insert_arcade_game(GameMedia{ fileName, item.path, item.name, gameName }); + continue; + } + else if (extension == "bin" || extension == "lst" || extension == "dat") + { + if (!config::HideLegacyNaomiRoms) + insert_arcade_game(GameMedia{ fileName, item.path, item.name, gameName }); + continue; + } + else if (extension == "chd" || extension == "gdi") + { + // Hide arcade gdroms + std::string basename = gameName; + string_tolower(basename); + if (arcade_gdroms.count(basename) != 0) + continue; + } + else if (extension != "cdi" && extension != "cue") + continue; + insert_game(GameMedia{ fileName, item.path, item.name, gameName }); + } +} + +void GameScanner::stop() +{ + LockGuard _(threadMutex); + running = false; + empty_folders_scanned = 0; + content_path_looks_incorrect = false; + if (scan_thread && scan_thread->joinable()) + scan_thread->join(); +} + +void GameScanner::fetch_game_list() +{ + LockGuard _(threadMutex); + if (scan_done || running) + return; + if (scan_thread && scan_thread->joinable()) + scan_thread->join(); + running = true; + scan_thread = std::make_unique([this]() + { + ThreadName _("GameScanner"); + if (arcade_games.empty()) + for (int gameid = 0; Games[gameid].name != nullptr; gameid++) + { + const Game *game = &Games[gameid]; + arcade_games[game->name] = game; + if (game->gdrom_name != nullptr) + arcade_gdroms.insert(game->gdrom_name); + } + { + LockGuard _(mutex); + game_list.clear(); + } + arcade_game_list.clear(); + for (const auto& path : config::ContentPath.get()) + { + try { + add_game_directory(path); + } catch (const hostfs::StorageException& e) { + // ignore + } + if (!running) + break; + } + { + LockGuard _(mutex); + game_list.insert(game_list.end(), arcade_game_list.begin(), arcade_game_list.end()); + } + if (running) + scan_done = true; + running = false; + }); +} diff --git a/core/ui/game_scanner.h b/core/ui/game_scanner.h index 33286b812..f6f5901d8 100644 --- a/core/ui/game_scanner.h +++ b/core/ui/game_scanner.h @@ -17,18 +17,15 @@ along with flycast. If not, see . */ #pragma once +#include "types.h" +#include "hw/naomi/naomi_roms.h" +#include #include +#include #include #include #include -#include "types.h" -#include "stdclass.h" -#include "hw/naomi/naomi_roms.h" -#include "oslib/oslib.h" -#include "oslib/storage.h" -#include "cfg/option.h" - struct GameMedia { std::string name; // Display name std::string path; // Full path to rom. May be an encoded uri @@ -36,11 +33,6 @@ struct GameMedia { std::string gameName; // for arcade games only, description from the rom list }; -static bool operator<(const GameMedia &left, const GameMedia &right) -{ - return left.name < right.name; -} - class GameScanner { std::vector game_list; @@ -52,85 +44,11 @@ class GameScanner bool running = false; std::unordered_map arcade_games; std::unordered_set arcade_gdroms; + using LockGuard = std::lock_guard; - void insert_game(const GameMedia& game) - { - std::lock_guard guard(mutex); - game_list.insert(std::upper_bound(game_list.begin(), game_list.end(), game), game); - } - - void insert_arcade_game(const GameMedia& game) - { - arcade_game_list.insert(std::upper_bound(arcade_game_list.begin(), arcade_game_list.end(), game), game); - } - - void add_game_directory(const std::string& path) - { - hostfs::DirectoryTree tree(path); - std::string emptyParentPath; - for (const hostfs::FileInfo& item : tree) - { - if (!running) - break; - - if (game_list.empty()) - { - // This won't work for android content uris - size_t slash = get_last_slash_pos(item.path); - std::string parentPath; - if (slash != 0 && slash != std::string::npos) - parentPath = item.path.substr(0, slash); - else - parentPath = item.path; - if (parentPath != emptyParentPath) - { - ++empty_folders_scanned; - emptyParentPath = parentPath; - if (empty_folders_scanned > 1000) - content_path_looks_incorrect = true; - } - } - else - { - content_path_looks_incorrect = false; - } - - if (item.name.substr(0, 2) == "._") - // Ignore Mac OS turds - continue; - std::string fileName(item.name); - std::string gameName(get_file_basename(item.name)); - std::string extension = get_file_extension(item.name); - if (extension == "zip" || extension == "7z") - { - string_tolower(gameName); - auto it = arcade_games.find(gameName); - if (it == arcade_games.end()) - continue; - gameName = it->second->description; - fileName = fileName + " (" + gameName + ")"; - insert_arcade_game(GameMedia{ fileName, item.path, item.name, gameName }); - continue; - } - else if (extension == "bin" || extension == "lst" || extension == "dat") - { - if (!config::HideLegacyNaomiRoms) - insert_arcade_game(GameMedia{ fileName, item.path, item.name, gameName }); - continue; - } - else if (extension == "chd" || extension == "gdi") - { - // Hide arcade gdroms - std::string basename = gameName; - string_tolower(basename); - if (arcade_gdroms.count(basename) != 0) - continue; - } - else if (extension != "cdi" && extension != "cue") - continue; - insert_game(GameMedia{ fileName, item.path, item.name, gameName }); - } - } + void insert_game(const GameMedia& game); + void insert_arcade_game(const GameMedia& game); + void add_game_directory(const std::string& path); public: ~GameScanner() @@ -143,60 +61,8 @@ public: scan_done = false; } - void stop() - { - std::lock_guard guard(threadMutex); - running = false; - empty_folders_scanned = 0; - content_path_looks_incorrect = false; - if (scan_thread && scan_thread->joinable()) - scan_thread->join(); - } - - void fetch_game_list() - { - std::lock_guard guard(threadMutex); - if (scan_done || running) - return; - if (scan_thread && scan_thread->joinable()) - scan_thread->join(); - running = true; - scan_thread = std::unique_ptr( - new std::thread([this]() - { - ThreadName _("GameScanner"); - if (arcade_games.empty()) - for (int gameid = 0; Games[gameid].name != nullptr; gameid++) - { - const Game *game = &Games[gameid]; - arcade_games[game->name] = game; - if (game->gdrom_name != nullptr) - arcade_gdroms.insert(game->gdrom_name); - } - { - std::lock_guard guard(mutex); - game_list.clear(); - } - arcade_game_list.clear(); - for (const auto& path : config::ContentPath.get()) - { - try { - add_game_directory(path); - } catch (const hostfs::StorageException& e) { - // ignore - } - if (!running) - break; - } - { - std::lock_guard guard(mutex); - game_list.insert(game_list.end(), arcade_game_list.begin(), arcade_game_list.end()); - } - if (running) - scan_done = true; - running = false; - })); - } + void stop(); + void fetch_game_list(); std::mutex& get_mutex() { return mutex; } const std::vector& get_game_list() { return game_list; } diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 8efddf932..3860735c2 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -49,6 +49,7 @@ #include "achievements/achievements.h" #include "gui_achievements.h" #include "IconsFontAwesome6.h" +#include "oslib/storage.h" #if defined(USE_SDL) #include "sdl/sdl.h" #endif @@ -698,6 +699,11 @@ static void gui_display_commands() dc_savestate(config::SavestateSlot); } + { + // Help with navigation with gamepad/keyboard + ImguiStyleVar _{ImGuiStyleVar_ItemSpacing, ImVec2(0.f, 2.f)}; + ImGui::Spacing(); + } // Slot # if (ImGui::ArrowButton("##prev-slot", ImGuiDir_Left)) { @@ -1150,7 +1156,6 @@ static void controller_mapping_popup(const std::shared_ptr& gamep gamepad->save_mapping(map_system); last_item_current_map_idx = 2; ImGui::EndPopup(); - ImGui::PopStyleVar(); return; } ImGui::SetItemDefaultFocus(); @@ -1390,7 +1395,6 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); - ImGui::PopStyleVar(); return; } ImGui::NewLine(); @@ -1426,7 +1430,6 @@ static void gamepadSettingsPopup(const std::shared_ptr& gamepad) ShowHelpMarker("Value sent to the game at 100% thumbstick deflection. " "Values greater than 100% will saturate before full deflection of the thumbstick."); } - ImGui::EndPopup(); } } @@ -3125,7 +3128,7 @@ static void gui_display_content() ImGui::SameLine(); counter++; // Put the image inside a child window so we can detect when it's fully clipped and doesn't need to be loaded - if (ImGui::BeginChild("img", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_None)) + if (ImGui::BeginChild("img", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_NavFlattened)) { ImguiTexture tex(art.boxartPath); pressed = gameImageButton(tex, game.name, responsiveBoxVec2, gameName); @@ -3159,7 +3162,6 @@ static void gui_display_content() scanner.get_mutex().unlock(); gui_start_game(gamePath); scanner.get_mutex().lock(); - ImGui::PopID(); break; } } diff --git a/core/ui/gui_achievements.cpp b/core/ui/gui_achievements.cpp index f9aa436e1..9daeecebe 100644 --- a/core/ui/gui_achievements.cpp +++ b/core/ui/gui_achievements.cpp @@ -306,7 +306,8 @@ void achievementList() gui_setState(GuiState::Commands); } - if (ImGui::BeginChild(ImGui::GetID("ach_list"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_DragScrolling | ImGuiWindowFlags_NavFlattened)) + // ImGuiWindowFlags_NavFlattened prevents the child window from getting the focus and thus the list can't be scrolled with a keyboard or gamepad. + if (ImGui::BeginChild(ImGui::GetID("ach_list"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_DragScrolling)) { std::vector achList = getAchievementList(); int id = 0; diff --git a/core/ui/gui_achievements.h b/core/ui/gui_achievements.h index 30aa7c188..a0ea9658a 100644 --- a/core/ui/gui_achievements.h +++ b/core/ui/gui_achievements.h @@ -17,7 +17,6 @@ along with Flycast. If not, see . */ #include "types.h" -#include "imgui.h" #include "gui_util.h" #include #include diff --git a/core/ui/gui_cheats.cpp b/core/ui/gui_cheats.cpp index 951905fbb..93a8d6725 100644 --- a/core/ui/gui_cheats.cpp +++ b/core/ui/gui_cheats.cpp @@ -20,55 +20,56 @@ #include "imgui.h" #include "gui_util.h" #include "cheats.h" +#include "IconsFontAwesome6.h" #ifdef __ANDROID__ #include "oslib/storage.h" #endif -static bool addingCheat; - static void addCheat() { static char cheatName[64]; static char cheatCode[128]; centerNextWindow(); ImGui::SetNextWindowSize(min(ImGui::GetIO().DisplaySize, ScaledVec2(600.f, 400.f))); + ImguiStyleVar _(ImGuiStyleVar_WindowBorderSize, 1); - ImGui::Begin("##main", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar - | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize); - + if (ImGui::BeginPopupModal("addCheat", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar + | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize)) { - ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); - ImGui::AlignTextToFramePadding(); - ImGui::Indent(uiScaled(10)); - ImGui::Text("ADD CHEAT"); + { + ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); + ImGui::AlignTextToFramePadding(); + ImGui::Indent(uiScaled(10)); + ImGui::Text("ADD CHEAT"); - ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Cancel").x - ImGui::GetStyle().FramePadding.x * 4.f - - ImGui::CalcTextSize("OK").x - ImGui::GetStyle().ItemSpacing.x); - if (ImGui::Button("Cancel")) - addingCheat = false; - ImGui::SameLine(); - if (ImGui::Button("OK")) - { - try { - cheatManager.addGameSharkCheat(cheatName, cheatCode); - addingCheat = false; - cheatName[0] = 0; - cheatCode[0] = 0; - } catch (const FlycastException& e) { - gui_error(e.what()); + ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Cancel").x - ImGui::GetStyle().FramePadding.x * 4.f + - ImGui::CalcTextSize("OK").x - ImGui::GetStyle().ItemSpacing.x); + if (ImGui::Button("Cancel")) + ImGui::CloseCurrentPopup(); + ImGui::SameLine(); + if (ImGui::Button("OK")) + { + try { + cheatManager.addGameSharkCheat(cheatName, cheatCode); + ImGui::CloseCurrentPopup(); + cheatName[0] = 0; + cheatCode[0] = 0; + } catch (const FlycastException& e) { + gui_error(e.what()); + } } + + ImGui::Unindent(uiScaled(10)); } - ImGui::Unindent(uiScaled(10)); + ImGui::BeginChild(ImGui::GetID("input"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_NavFlattened); + { + ImGui::InputText("Name", cheatName, sizeof(cheatName), 0, nullptr, nullptr); + ImGui::InputTextMultiline("Code", cheatCode, sizeof(cheatCode), ImVec2(0, ImGui::GetTextLineHeight() * 8), 0, nullptr, nullptr); + } + ImGui::EndChild(); + ImGui::EndPopup(); } - - ImGui::BeginChild(ImGui::GetID("input"), ImVec2(0, 0), ImGuiChildFlags_Border, ImGuiWindowFlags_NavFlattened); - { - ImGui::InputText("Name", cheatName, sizeof(cheatName), 0, nullptr, nullptr); - ImGui::InputTextMultiline("Code", cheatCode, sizeof(cheatCode), ImVec2(0, ImGui::GetTextLineHeight() * 8), 0, nullptr, nullptr); - } - ImGui::EndChild(); - ImGui::End(); } static void cheatFileSelected(bool cancelled, std::string path) @@ -79,13 +80,8 @@ static void cheatFileSelected(bool cancelled, std::string path) void gui_cheats() { - if (addingCheat) - { - addCheat(); - return; - } - centerNextWindow(); - ImGui::SetNextWindowSize(min(ImGui::GetIO().DisplaySize, ScaledVec2(600.f, 400.f))); + fullScreenWindow(false); + ImguiStyleVar _(ImGuiStyleVar_WindowBorderSize, 0); ImGui::Begin("##main", nullptr, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize); @@ -94,12 +90,13 @@ void gui_cheats() ImguiStyleVar _(ImGuiStyleVar_FramePadding, ScaledVec2(20, 8)); ImGui::AlignTextToFramePadding(); ImGui::Indent(uiScaled(10)); - ImGui::Text("CHEATS"); + ImGui::Text(ICON_FA_MASK " CHEATS"); ImGui::SameLine(ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Add").x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().FramePadding.x * 6.f - ImGui::CalcTextSize("Load").x - ImGui::GetStyle().ItemSpacing.x * 2); if (ImGui::Button("Add")) - addingCheat = true; + ImGui::OpenPopup("addCheat"); + addCheat(); ImGui::SameLine(); #ifdef __ANDROID__ if (ImGui::Button("Load")) From 83493cc14b1f08388378c9e06834d525844f77db Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Thu, 9 May 2024 18:26:28 +0200 Subject: [PATCH 68/86] achievements: fix size of multiple challenge indicators popup Issue #761 --- core/ui/gui_achievements.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/ui/gui_achievements.cpp b/core/ui/gui_achievements.cpp index 9daeecebe..7a8d84b29 100644 --- a/core/ui/gui_achievements.cpp +++ b/core/ui/gui_achievements.cpp @@ -175,8 +175,9 @@ bool Notification::draw() if (type == Challenge) { const ScaledVec2 size(60.f, 60.f); - const ImVec2 spacing(ImGui::GetStyle().ItemSpacing.x, 0.f); - const ImVec2 totalSize = size * challenges.size() + spacing * (challenges.size() - 1) + padding * 2.f; + const float hspacing = ImGui::GetStyle().ItemSpacing.x; + ImVec2 totalSize = padding * 2 + size; + totalSize.x += (size.x + hspacing) * (challenges.size() - 1); ImVec2 pos(insetLeft, ImGui::GetIO().DisplaySize.y - totalSize.y * (1.f - animY)); dl->AddRectFilled(pos, pos + totalSize, bg_col, 0.f); dl->AddRect(pos, pos + totalSize, borderCol, 0.f); @@ -184,7 +185,7 @@ bool Notification::draw() pos += padding; for (const auto& img : challenges) { img.draw(dl, pos, size, alpha); - pos += spacing; + pos.x += hspacing + size.x; } } else if (type == Leaderboard) From 6f0581032b57c6664bae1281e88e16491b615a79 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 13 May 2024 15:47:34 +0200 Subject: [PATCH 69/86] save screenshot. add screenshot to savestates Retrieve last frame rgb data (gl, vk, dx9, dx11). Specific save screenshot code for android, iOS and UWP. Add Save Screenshot emu key (F12 by default) vk: defer deletion of in-flight textures when texture cache is cleared. vk: fix issue when updating imgui textures after a render pass has begun (achievements) vk: palette texture not updated after a state has been loaded. gl: Move opengl-specific stuff into opengl imgui driver. savestate: Add non compressed header, following by screenshot png data, before actual savestate. Issue #842 --- CMakeLists.txt | 1 - core/archive/rzip.cpp | 30 +- core/archive/rzip.h | 2 + core/emulator.cpp | 6 +- core/emulator.h | 6 +- core/hw/pvr/Renderer_if.h | 4 + core/input/gamepad.h | 1 + core/input/gamepad_device.cpp | 4 + core/input/keyboard_device.h | 1 + core/input/mapping.cpp | 1 + core/nullDC.cpp | 188 +++++++---- core/oslib/oslib.cpp | 116 +++++++ core/oslib/oslib.h | 2 + core/rend/dx11/dx11_driver.h | 4 + core/rend/dx11/dx11_renderer.cpp | 80 +++++ core/rend/dx11/dx11_renderer.h | 1 + core/rend/dx9/d3d_renderer.cpp | 90 ++++++ core/rend/dx9/d3d_renderer.h | 1 + core/rend/dx9/dx9_driver.h | 6 +- core/rend/gles/gldraw.cpp | 64 +++- core/rend/gles/gles.cpp | 4 +- core/rend/gles/gles.h | 3 +- core/rend/gles/gltex.cpp | 24 +- core/rend/gles/opengl_driver.cpp | 37 +++ core/rend/gles/opengl_driver.h | 5 + core/rend/gles/quad.cpp | 2 +- core/rend/vulkan/buffer.h | 4 +- core/rend/vulkan/texture.h | 7 +- core/rend/vulkan/vk_context_lr.h | 2 + core/rend/vulkan/vulkan_context.cpp | 106 +++++++ core/rend/vulkan/vulkan_context.h | 2 + core/rend/vulkan/vulkan_driver.h | 21 ++ core/rend/vulkan/vulkan_renderer.cpp | 275 ++++++++++++++++ core/rend/vulkan/vulkan_renderer.h | 294 +----------------- core/serialize.cpp | 56 ++++ core/serialize.h | 33 +- core/stdclass.h | 38 +++ core/ui/gui.cpp | 187 ++++++++--- core/ui/gui.h | 4 +- core/ui/gui_achievements.cpp | 8 +- core/ui/gui_achievements.h | 4 +- core/ui/gui_util.cpp | 172 ++++++++-- core/ui/gui_util.h | 61 +++- core/ui/imgui_driver.cpp | 110 ------- core/ui/imgui_driver.h | 49 ++- core/ui/mainui.cpp | 2 +- .../com/flycast/emulator/AndroidStorage.java | 36 +++ .../src/main/jni/src/android_storage.h | 16 + shell/apple/emulator-ios/emulator/ios_main.mm | 13 + shell/apple/emulator-ios/plist.in | 2 + .../emulator-osx/emulator-osx/osx-main.mm | 11 + shell/libretro/oslib.cpp | 10 - 52 files changed, 1562 insertions(+), 644 deletions(-) delete mode 100644 core/ui/imgui_driver.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 640375fe1..d2c6fc0a7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1294,7 +1294,6 @@ if(NOT LIBRETRO) target_sources(${PROJECT_NAME} PRIVATE core/ui/game_scanner.cpp core/ui/game_scanner.h - core/ui/imgui_driver.cpp core/ui/imgui_driver.h core/ui/gui.cpp core/ui/gui.h diff --git a/core/archive/rzip.cpp b/core/archive/rzip.cpp index 43109e21f..9a965e8f5 100644 --- a/core/archive/rzip.cpp +++ b/core/archive/rzip.cpp @@ -23,14 +23,11 @@ const u8 RZipHeader[8] = { '#', 'R', 'Z', 'I', 'P', 'v', 1, '#' }; -bool RZipFile::Open(const std::string& path, bool write) +bool RZipFile::Open(FILE *file, bool write) { - verify(file == nullptr); - this->write = write; - - file = nowide::fopen(path.c_str(), write ? "wb" : "rb"); - if (file == nullptr) - return false; + verify(this->file == nullptr); + verify(file != nullptr); + startOffset = std::ftell(file); if (!write) { u8 header[sizeof(RZipHeader)]; @@ -39,7 +36,7 @@ bool RZipFile::Open(const std::string& path, bool write) || std::fread(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1 || std::fread(&size, sizeof(size), 1, file) != 1) { - Close(); + std::fseek(file, startOffset, SEEK_SET); return false; } // savestates created on 32-bit platforms used to have a 32-bit size @@ -59,11 +56,24 @@ bool RZipFile::Open(const std::string& path, bool write) || std::fwrite(&maxChunkSize, sizeof(maxChunkSize), 1, file) != 1 || std::fwrite(&size, sizeof(size), 1, file) != 1) { - Close(); + std::fseek(file, startOffset, SEEK_SET); return false; } } + this->write = write; + this->file = file; + return true; +} +bool RZipFile::Open(const std::string& path, bool write) +{ + FILE *f = nowide::fopen(path.c_str(), write ? "wb" : "rb"); + if (f == nullptr) + return false; + if (!Open(f, write)) { + Close(); + return false; + } return true; } @@ -73,7 +83,7 @@ void RZipFile::Close() { if (write) { - std::fseek(file, sizeof(RZipHeader) + sizeof(maxChunkSize), SEEK_SET); + std::fseek(file, startOffset + sizeof(RZipHeader) + sizeof(maxChunkSize), SEEK_SET); std::fwrite(&size, sizeof(size), 1, file); } std::fclose(file); diff --git a/core/archive/rzip.h b/core/archive/rzip.h index 6d2dc287f..0d0edf9a4 100644 --- a/core/archive/rzip.h +++ b/core/archive/rzip.h @@ -28,6 +28,7 @@ public: ~RZipFile() { Close(); } bool Open(const std::string& path, bool write); + bool Open(FILE *file, bool write); void Close(); size_t Size() const { return size; } size_t Read(void *data, size_t length); @@ -42,4 +43,5 @@ private: u32 chunkSize = 0; u32 chunkIndex = 0; bool write = false; + long startOffset = 0; }; diff --git a/core/emulator.cpp b/core/emulator.cpp index 9af7f6f5b..d41b93eaa 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -550,10 +550,12 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) settings.input.fastForwardMode = false; if (!settings.content.path.empty()) { +#ifndef LIBRETRO if (config::GGPOEnable) dc_loadstate(-1); else if (config::AutoLoadState && !NaomiNetworkSupported() && !settings.naomi.multiboard) dc_loadstate(config::SavestateSlot); +#endif } EventManager::event(Event::Start); @@ -612,9 +614,11 @@ void Emulator::unloadGame() } catch (...) { } if (state == Loaded || state == Error) { +#ifndef LIBRETRO if (state == Loaded && config::AutoSaveState && !settings.content.path.empty() && !settings.naomi.multiboard && !config::GGPOEnable && !NaomiNetworkSupported()) - dc_savestate(config::SavestateSlot); + gui_saveState(false); +#endif try { dc_reset(true); } catch (const FlycastException& e) { diff --git a/core/emulator.h b/core/emulator.h index f23835441..6802b00e8 100644 --- a/core/emulator.h +++ b/core/emulator.h @@ -27,6 +27,7 @@ #include #include #include +#include void loadGameSpecificSettings(); void SaveSettings(); @@ -35,10 +36,11 @@ int flycast_init(int argc, char* argv[]); void dc_reset(bool hard); // for tests only void flycast_term(); void dc_exit(); -void dc_savestate(int index = 0); +void dc_savestate(int index = 0, const u8 *pngData = nullptr, u32 pngSize = 0); void dc_loadstate(int index = 0); void dc_loadstate(Deserializer& deser); -std::string dc_getStateUpdateDate(int index); +time_t dc_getStateCreationDate(int index); +void dc_getStateScreenshot(int index, std::vector& pngData); enum class Event { Start, diff --git a/core/hw/pvr/Renderer_if.h b/core/hw/pvr/Renderer_if.h index 71d306711..191368630 100644 --- a/core/hw/pvr/Renderer_if.h +++ b/core/hw/pvr/Renderer_if.h @@ -1,6 +1,7 @@ #pragma once #include "types.h" #include "ta_ctx.h" +#include extern u32 FrameCount; @@ -62,6 +63,9 @@ struct Renderer virtual bool Render() = 0; virtual void RenderFramebuffer(const FramebufferInfo& info) = 0; virtual bool RenderLastFrame() { return false; } + // Get the last rendered frame pixel data in RGB format + // The returned image is rotated and scaled (upward orientation and square pixels) + virtual bool GetLastFrame(std::vector& data, int& width, int& height) { return false; } virtual bool Present() { return true; } diff --git a/core/input/gamepad.h b/core/input/gamepad.h index e428b62c1..88bce6fe7 100644 --- a/core/input/gamepad.h +++ b/core/input/gamepad.h @@ -51,6 +51,7 @@ enum DreamcastKey EMU_BTN_LOADSTATE, EMU_BTN_SAVESTATE, EMU_BTN_BYPASS_KB, + EMU_BTN_SCREENSHOT, // Real axes DC_AXIS_TRIGGERS = 0x1000000, diff --git a/core/input/gamepad_device.cpp b/core/input/gamepad_device.cpp index 610ef5b85..7ca658414 100644 --- a/core/input/gamepad_device.cpp +++ b/core/input/gamepad_device.cpp @@ -99,6 +99,10 @@ bool GamepadDevice::handleButtonInput(int port, DreamcastKey key, bool pressed) if (pressed) gui_saveState(); break; + case EMU_BTN_SCREENSHOT: + if (pressed) + gui_takeScreenshot(); + break; case DC_AXIS_LT: if (port >= 0) lt[port] = pressed ? 0xffff : 0; diff --git a/core/input/keyboard_device.h b/core/input/keyboard_device.h index 1ae17ce35..682408556 100644 --- a/core/input/keyboard_device.h +++ b/core/input/keyboard_device.h @@ -61,6 +61,7 @@ public: set_button(DC_AXIS_LEFT, 13); // J set_button(DC_AXIS_RIGHT, 15); // L set_button(DC_BTN_D, 4); // Q (Coin) + set_button(EMU_BTN_SCREENSHOT, 69); // F12 dirty = false; } diff --git a/core/input/mapping.cpp b/core/input/mapping.cpp index 336fc8bd4..fdea33989 100644 --- a/core/input/mapping.cpp +++ b/core/input/mapping.cpp @@ -61,6 +61,7 @@ button_list[] = { EMU_BTN_LOADSTATE, "emulator", "btn_jump_state" }, { EMU_BTN_SAVESTATE, "emulator", "btn_quick_save" }, { EMU_BTN_BYPASS_KB, "emulator", "btn_bypass_kb" }, + { EMU_BTN_SCREENSHOT, "emulator", "btn_screenshot" }, }; static struct diff --git a/core/nullDC.cpp b/core/nullDC.cpp index be8955bf2..5741127a2 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -17,6 +17,29 @@ #include "serialize.h" #include +struct SavestateHeader +{ + void init() + { + memcpy(magic, MAGIC, sizeof(magic)); + creationDate = time(nullptr); + version = Deserializer::Current; + pngSize = 0; + } + + bool isValid() const { + return !memcmp(magic, MAGIC, sizeof(magic)); + } + + char magic[8]; + u64 creationDate; + u32 version; + u32 pngSize; + // png data + + static constexpr const char *MAGIC = "FLYSAVE1"; +}; + int flycast_init(int argc, char* argv[]) { #if defined(TEST_AUTOMATION) @@ -67,7 +90,7 @@ int flycast_init(int argc, char* argv[]) void dc_exit() { try { - emu.stop(); + emu.unloadGame(); } catch (...) { } mainui_stop(); } @@ -85,15 +108,15 @@ void SaveSettings() void flycast_term() { - os_DestroyWindow(); gui_cancel_load(); lua::term(); emu.term(); + os_DestroyWindow(); gui_term(); os_TermInput(); } -void dc_savestate(int index) +void dc_savestate(int index, const u8 *pngData, u32 pngSize) { if (settings.network.online) return; @@ -113,10 +136,8 @@ void dc_savestate(int index) dc_serialize(ser); std::string filename = hostfs::getSavestatePath(index, true); -#if 0 FILE *f = nowide::fopen(filename.c_str(), "wb"); - - if ( f == NULL ) + if (f == nullptr) { WARN_LOG(SAVESTATE, "Failed to save state - could not open %s for writing", filename.c_str()); gui_display_notification("Cannot open save file", 5000); @@ -124,31 +145,41 @@ void dc_savestate(int index) return; } + RZipFile zipFile; + SavestateHeader header; + header.init(); + header.pngSize = pngSize; + if (std::fwrite(&header, sizeof(header), 1, f) != 1) + goto fail; + if (pngSize > 0 && std::fwrite(pngData, 1, pngSize, f) != pngSize) + goto fail; + +#if 0 + // Uncompressed savestate std::fwrite(data, 1, ser.size(), f); std::fclose(f); #else - RZipFile zipFile; - if (!zipFile.Open(filename, true)) - { - WARN_LOG(SAVESTATE, "Failed to save state - could not open %s for writing", filename.c_str()); - gui_display_notification("Cannot open save file", 5000); - free(data); - return; - } + if (!zipFile.Open(f, true)) + goto fail; if (zipFile.Write(data, ser.size()) != ser.size()) - { - WARN_LOG(SAVESTATE, "Failed to save state - error writing %s", filename.c_str()); - gui_display_notification("Error saving state", 5000); - zipFile.Close(); - free(data); - return; - } + goto fail; zipFile.Close(); #endif free(data); NOTICE_LOG(SAVESTATE, "Saved state to %s size %d", filename.c_str(), (int)ser.size()); gui_display_notification("State saved", 2000); + return; + +fail: + WARN_LOG(SAVESTATE, "Failed to save state - error writing %s", filename.c_str()); + gui_display_notification("Error saving state", 5000); + if (zipFile.rawFile() != nullptr) + zipFile.Close(); + else + std::fclose(f); + free(data); + // delete failed savestate? } void dc_loadstate(int index) @@ -156,46 +187,54 @@ void dc_loadstate(int index) if (settings.raHardcoreMode) return; u32 total_size = 0; - FILE *f = nullptr; std::string filename = hostfs::getSavestatePath(index, false); - RZipFile zipFile; - if (zipFile.Open(filename, false)) + FILE *f = nowide::fopen(filename.c_str(), "rb"); + if (f == nullptr) { + WARN_LOG(SAVESTATE, "Failed to load state - could not open %s for reading", filename.c_str()); + gui_display_notification("Save state not found", 2000); + return; + } + SavestateHeader header; + if (std::fread(&header, sizeof(header), 1, f) == 1) + { + if (!header.isValid()) + // seek to beginning of file if this isn't a valid header (legacy savestate) + std::fseek(f, 0, SEEK_SET); + else + // skip png data + std::fseek(f, header.pngSize, SEEK_CUR); + } + else { + // probably not a valid savestate but we'll fail later + std::fseek(f, 0, SEEK_SET); + } + + if (index == -1 && config::GGPOEnable) + { + long pos = std::ftell(f); + MD5Sum().add(f) + .getDigest(settings.network.md5.savestate); + std::fseek(f, pos, SEEK_SET); + } + RZipFile zipFile; + if (zipFile.Open(f, false)) { total_size = (u32)zipFile.Size(); - if (index == -1 && config::GGPOEnable) - { - f = zipFile.rawFile(); - long pos = std::ftell(f); - MD5Sum().add(f) - .getDigest(settings.network.md5.savestate); - std::fseek(f, pos, SEEK_SET); - f = nullptr; - } } else { - f = nowide::fopen(filename.c_str(), "rb"); - - if ( f == NULL ) - { - WARN_LOG(SAVESTATE, "Failed to load state - could not open %s for reading", filename.c_str()); - gui_display_notification("Save state not found", 2000); - return; - } - if (index == -1 && config::GGPOEnable) - MD5Sum().add(f) - .getDigest(settings.network.md5.savestate); + long pos = std::ftell(f); std::fseek(f, 0, SEEK_END); - total_size = (u32)std::ftell(f); - std::fseek(f, 0, SEEK_SET); + total_size = (u32)std::ftell(f) - pos; + std::fseek(f, pos, SEEK_SET); } void *data = malloc(total_size); if (data == nullptr) { WARN_LOG(SAVESTATE, "Failed to load state - could not malloc %d bytes", total_size); gui_display_notification("Failed to load state - memory full", 5000); - if (f != nullptr) + if (zipFile.rawFile() == nullptr) std::fclose(f); else zipFile.Close(); @@ -203,14 +242,14 @@ void dc_loadstate(int index) } size_t read_size; - if (f == nullptr) + if (zipFile.rawFile() != nullptr) { read_size = zipFile.Read(data, total_size); zipFile.Close(); } else { - read_size = fread(data, 1, total_size, f); + read_size = std::fread(data, 1, total_size, f); std::fclose(f); } if (read_size != total_size) @@ -226,6 +265,7 @@ void dc_loadstate(int index) dc_loadstate(deser); NOTICE_LOG(SAVESTATE, "Loaded state ver %d from %s size %d", deser.version(), filename.c_str(), total_size); if (deser.size() != total_size) + // Note: this isn't true for RA savestates WARN_LOG(SAVESTATE, "Savestate size %d but only %d bytes used", total_size, (int)deser.size()); } catch (const Deserializer::Exception& e) { ERROR_LOG(SAVESTATE, "%s", e.what()); @@ -235,25 +275,41 @@ void dc_loadstate(int index) EventManager::event(Event::LoadState); } -#ifdef _WIN32 -static struct tm *localtime_r(const time_t *_clock, struct tm *_result) -{ - return localtime_s(_result, _clock) ? nullptr : _result; -} -#endif - -std::string dc_getStateUpdateDate(int index) +time_t dc_getStateCreationDate(int index) { std::string filename = hostfs::getSavestatePath(index, false); - struct stat st; - if (flycast::stat(filename.c_str(), &st) != 0) - return {}; - tm t; - if (localtime_r(&st.st_mtime, &t) == nullptr) - return {}; - std::string s(32, '\0'); - s.resize(snprintf(s.data(), 32, "%04d/%02d/%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)); - return s; + FILE *f = nowide::fopen(filename.c_str(), "rb"); + if (f == nullptr) + return 0; + SavestateHeader header; + if (std::fread(&header, sizeof(header), 1, f) != 1 || !header.isValid()) + { + std::fclose(f); + struct stat st; + if (flycast::stat(filename.c_str(), &st) == 0) + return st.st_mtime; + else + return 0; + } + std::fclose(f); + return (time_t)header.creationDate; +} + +void dc_getStateScreenshot(int index, std::vector& pngData) +{ + pngData.clear(); + std::string filename = hostfs::getSavestatePath(index, false); + FILE *f = nowide::fopen(filename.c_str(), "rb"); + if (f == nullptr) + return; + SavestateHeader header; + if (std::fread(&header, sizeof(header), 1, f) == 1 && header.isValid() && header.pngSize != 0) + { + pngData.resize(header.pngSize); + if (std::fread(pngData.data(), 1, pngData.size(), f) != pngData.size()) + pngData.clear(); + } + std::fclose(f); } #endif diff --git a/core/oslib/oslib.cpp b/core/oslib/oslib.cpp index ad3b0561b..834ade0ef 100644 --- a/core/oslib/oslib.cpp +++ b/core/oslib/oslib.cpp @@ -37,6 +37,7 @@ #endif #if defined(_WIN32) && !defined(TARGET_UWP) #include "windows/rawinput.h" +#include #endif #include "profiler/fc_profiler.h" @@ -151,8 +152,123 @@ std::string getTextureDumpPath() return get_writable_data_path("texdump/"); } +#if defined(__unix__) && !defined(__ANDROID__) + +static std::string runCommand(const std::string& cmd) +{ + char buf[1024] {}; + FILE *fp = popen(cmd.c_str(), "r"); + if (fp == nullptr) { + INFO_LOG(COMMON, "popen failed: %d", errno); + return ""; + } + std::string result; + while (fgets(buf, sizeof(buf), fp) != nullptr) + result += trim_trailing_ws(buf, "\n"); + + int rc; + if ((rc = pclose(fp)) != 0) { + INFO_LOG(COMMON, "Command error: %d", rc); + return ""; + } + + return result; } +static std::string getScreenshotsPath() +{ + std::string picturesPath = runCommand("xdg-user-dir PICTURES"); + if (!picturesPath.empty()) + return picturesPath; + const char *home = nowide::getenv("HOME"); + if (home != nullptr) + return home; + else + return "."; +} + +#elif defined(TARGET_UWP) +//TODO move to shell/uwp? +using namespace Platform; +using namespace Windows::Foundation; +using namespace Windows::Storage; + +void saveScreenshot(const std::string& name, const std::vector& data) +{ + try { + StorageFolder^ folder = KnownFolders::PicturesLibrary; // or SavedPictures? + if (folder == nullptr) { + INFO_LOG(COMMON, "KnownFolders::PicturesLibrary is null"); + throw FlycastException(); + } + nowide::wstackstring wstr; + wchar_t *wname = wstr.convert(name.c_str()); + String^ msname = ref new String(wname); + ArrayReference arrayRef(const_cast(&data[0]), data.size()); + + IAsyncOperation^ op = folder->CreateFileAsync(msname, CreationCollisionOption::FailIfExists); + cResetEvent asyncEvent; + op->Completed = ref new AsyncOperationCompletedHandler( + [&asyncEvent, &arrayRef](IAsyncOperation^ op, AsyncStatus) { + IAsyncAction^ action = FileIO::WriteBytesAsync(op->GetResults(), arrayRef); + action->Completed = ref new AsyncActionCompletedHandler( + [&asyncEvent](IAsyncAction^, AsyncStatus){ + asyncEvent.Set(); + }); + }); + asyncEvent.Wait(); + } + catch (COMException^ e) { + WARN_LOG(COMMON, "Save screenshot failed: %S", e->Message->Data()); + throw FlycastException(); + } +} + +#elif defined(_WIN32) && !defined(TARGET_UWP) + +static std::string getScreenshotsPath() +{ + wchar_t *screenshotPath; + if (FAILED(SHGetKnownFolderPath(FOLDERID_Screenshots, KF_FLAG_DEFAULT, NULL, &screenshotPath))) + return get_writable_config_path(""); + nowide::stackstring path; + std::string ret; + if (path.convert(screenshotPath) == nullptr) + ret = get_writable_config_path(""); + else + ret = path.get(); + CoTaskMemFree(screenshotPath); + + return ret; +} + +#else + +std::string getScreenshotsPath(); + +#endif + +#if !defined(__ANDROID__) && !defined(TARGET_UWP) && !defined(TARGET_IPHONE) && !defined(__SWITCH__) + +void saveScreenshot(const std::string& name, const std::vector& data) +{ + std::string path = getScreenshotsPath(); + path += "/" + name; + FILE *f = nowide::fopen(path.c_str(), "wb"); + if (f == nullptr) + throw FlycastException(path); + if (std::fwrite(&data[0], data.size(), 1, f) != 1) { + std::fclose(f); + unlink(path.c_str()); + throw FlycastException(path); + } + std::fclose(f); +} + +#endif + +} // namespace hostfs + void os_CreateWindow() { #if defined(USE_SDL) diff --git a/core/oslib/oslib.h b/core/oslib/oslib.h index 8744af19e..8c64fd4df 100644 --- a/core/oslib/oslib.h +++ b/core/oslib/oslib.h @@ -1,5 +1,6 @@ #pragma once #include "types.h" +#include #if defined(__SWITCH__) #include #endif @@ -59,6 +60,7 @@ namespace hostfs std::string getTextureDumpPath(); std::string getShaderCachePath(const std::string& filename); + void saveScreenshot(const std::string& name, const std::vector& data); } static inline void *allocAligned(size_t alignment, size_t size) diff --git a/core/rend/dx11/dx11_driver.h b/core/rend/dx11/dx11_driver.h index 5807c2f01..446de5926 100644 --- a/core/rend/dx11/dx11_driver.h +++ b/core/rend/dx11/dx11_driver.h @@ -92,6 +92,10 @@ public: return (ImTextureID)&texture.imTexture; } + void deleteTexture(const std::string& name) override { + textures.erase(name); + } + private: struct Texture { diff --git a/core/rend/dx11/dx11_renderer.cpp b/core/rend/dx11/dx11_renderer.cpp index b52e8fd3f..2cb9e9357 100644 --- a/core/rend/dx11/dx11_renderer.cpp +++ b/core/rend/dx11/dx11_renderer.cpp @@ -1345,6 +1345,86 @@ void DX11Renderer::writeFramebufferToVRAM() WriteFramebuffer<2, 1, 0, 3>(width, height, (u8 *)tmp_buf.data(), texAddress, pvrrc.fb_W_CTRL, linestride, xClip, yClip); } +bool DX11Renderer::GetLastFrame(std::vector& data, int& width, int& height) +{ + if (!frameRenderedOnce) + return false; + + width = this->width; + height = this->height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = aspectRatio * height; + if (width > w) + height = width / aspectRatio; + else + width = w; + + ComPtr dstTex; + ComPtr dstRenderTarget; + createTexAndRenderTarget(dstTex, dstRenderTarget, width, height); + + ID3D11ShaderResourceView *nullResView = nullptr; + deviceContext->PSSetShaderResources(0, 1, &nullResView); + deviceContext->OMSetRenderTargets(1, &dstRenderTarget.get(), nullptr); + D3D11_VIEWPORT vp{}; + vp.Width = (FLOAT)width; + vp.Height = (FLOAT)height; + vp.MinDepth = 0.f; + vp.MaxDepth = 1.f; + deviceContext->RSSetViewports(1, &vp); + const D3D11_RECT r = { 0, 0, (LONG)width, (LONG)height }; + deviceContext->RSSetScissorRects(1, &r); + deviceContext->OMSetBlendState(blendStates.getState(false), nullptr, 0xffffffff); + deviceContext->GSSetShader(nullptr, nullptr, 0); + deviceContext->HSSetShader(nullptr, nullptr, 0); + deviceContext->DSSetShader(nullptr, nullptr, 0); + deviceContext->CSSetShader(nullptr, nullptr, 0); + + quad->draw(fbTextureView, samplers->getSampler(true), nullptr, -1.f, -1.f, 2.f, 2.f, config::Rotate90); + + deviceContext->OMSetRenderTargets(1, &theDX11Context.getRenderTarget().get(), nullptr); + + D3D11_TEXTURE2D_DESC desc; + dstTex->GetDesc(&desc); + desc.Usage = D3D11_USAGE_STAGING; + desc.BindFlags = 0; + desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + + ComPtr stagingTex; + HRESULT hr = device->CreateTexture2D(&desc, nullptr, &stagingTex.get()); + if (FAILED(hr)) + { + WARN_LOG(RENDERER, "Staging screenshot texture creation failed"); + return false; + } + deviceContext->CopyResource(stagingTex, dstTex); + + D3D11_MAPPED_SUBRESOURCE mappedSubres; + hr = deviceContext->Map(stagingTex, 0, D3D11_MAP_READ, 0, &mappedSubres); + if (FAILED(hr)) + { + WARN_LOG(RENDERER, "Failed to map staging screenshot texture"); + return false; + } + const u8* const src = (const u8 *)mappedSubres.pData; + for (int y = 0; y < height; y++) + { + const u8 *p = src + y * mappedSubres.RowPitch; + for (int x = 0; x < width; x++, p += 4) + { + data.push_back(p[2]); + data.push_back(p[1]); + data.push_back(p[0]); + } + } + deviceContext->Unmap(stagingTex, 0); + + return true; +} + void DX11Renderer::renderVideoRouting() { #ifdef VIDEO_ROUTING diff --git a/core/rend/dx11/dx11_renderer.h b/core/rend/dx11/dx11_renderer.h index e5f067a77..6304cb652 100644 --- a/core/rend/dx11/dx11_renderer.h +++ b/core/rend/dx11/dx11_renderer.h @@ -53,6 +53,7 @@ struct DX11Renderer : public Renderer bool RenderLastFrame() override; void DrawOSD(bool clear_screen) override; BaseTextureCacheData *GetTexture(TSP tsp, TCW tcw) override; + bool GetLastFrame(std::vector& data, int& width, int& height) override; protected: struct VertexConstants diff --git a/core/rend/dx9/d3d_renderer.cpp b/core/rend/dx9/d3d_renderer.cpp index 42d892395..ac58ab35a 100644 --- a/core/rend/dx9/d3d_renderer.cpp +++ b/core/rend/dx9/d3d_renderer.cpp @@ -1422,6 +1422,96 @@ void D3DRenderer::writeFramebufferToVRAM() WriteFramebuffer<2, 1, 0, 3>(width, height, (u8 *)tmp_buf.data(), texAddress, pvrrc.fb_W_CTRL, linestride, xClip, yClip); } +bool D3DRenderer::GetLastFrame(std::vector& data, int& width, int& height) +{ + if (!frameRenderedOnce || !theDXContext.isReady()) + return false; + + width = this->width; + height = this->height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = aspectRatio * height; + if (width > w) + height = width / aspectRatio; + else + width = w; + + backbuffer.reset(); + device->GetRenderTarget(0, &backbuffer.get()); + + // Target texture and surface + ComPtr target; + device->CreateTexture(width, height, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &target.get(), NULL); + ComPtr surface; + target->GetSurfaceLevel(0, &surface.get()); + device->SetRenderTarget(0, surface); + // Draw + devCache.SetRenderState(D3DRS_SCISSORTESTENABLE, FALSE); + device->SetPixelShader(NULL); + device->SetVertexShader(NULL); + device->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); + device->SetRenderState(D3DRS_ZENABLE, FALSE); + device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE); + device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); + device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); + device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); + + glm::mat4 identity = glm::identity(); + glm::mat4 projection = glm::translate(glm::vec3(-1.f / width, 1.f / height, 0)); + if (config::Rotate90) + projection *= glm::rotate((float)M_PI_2, glm::vec3(0, 0, 1)); + + device->SetTransform(D3DTS_WORLD, (const D3DMATRIX *)&identity[0][0]); + device->SetTransform(D3DTS_VIEW, (const D3DMATRIX *)&identity[0][0]); + device->SetTransform(D3DTS_PROJECTION, (const D3DMATRIX *)&projection[0][0]); + + device->SetFVF(D3DFVF_XYZ | D3DFVF_TEX1); + D3DVIEWPORT9 viewport{}; + viewport.Width = width; + viewport.Height = height; + viewport.MaxZ = 1; + bool rc = SUCCEEDED(device->SetViewport(&viewport)); + verify(rc); + float coords[] { + -1, 1, 0.5f, 0, 0, + -1, -1, 0.5f, 0, 1, + 1, 1, 0.5f, 1, 0, + 1, -1, 0.5f, 1, 1, + }; + device->SetTexture(0, framebufferTexture); + device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, coords, sizeof(float) * 5); + + // Copy back + ComPtr offscreenSurface; + rc = SUCCEEDED(device->CreateOffscreenPlainSurface(width, height, D3DFMT_A8R8G8B8, D3DPOOL_SYSTEMMEM, &offscreenSurface.get(), nullptr)); + verify(rc); + rc = SUCCEEDED(device->GetRenderTargetData(surface, offscreenSurface)); + verify(rc); + + D3DLOCKED_RECT rect; + RECT lockRect { 0, 0, (long)width, (long)height }; + rc = SUCCEEDED(offscreenSurface->LockRect(&rect, &lockRect, D3DLOCK_READONLY)); + verify(rc); + data.clear(); + data.reserve(width * height * 3); + for (int y = 0; y < height; y++) + { + const u8 *src = (const u8 *)rect.pBits + y * rect.Pitch; + for (int x = 0; x < width; x++, src += 4) + { + data.push_back(src[2]); + data.push_back(src[1]); + data.push_back(src[0]); + } + } + rc = SUCCEEDED(offscreenSurface->UnlockRect()); + device->SetRenderTarget(0, backbuffer); + + return true; +} + Renderer* rend_DirectX9() { return new D3DRenderer(); diff --git a/core/rend/dx9/d3d_renderer.h b/core/rend/dx9/d3d_renderer.h index 07787564c..bdea5cbe9 100644 --- a/core/rend/dx9/d3d_renderer.h +++ b/core/rend/dx9/d3d_renderer.h @@ -116,6 +116,7 @@ struct D3DRenderer : public Renderer void preReset(); void postReset(); void RenderFramebuffer(const FramebufferInfo& info) override; + bool GetLastFrame(std::vector& data, int& width, int& height) override; private: enum ModifierVolumeMode { Xor, Or, Inclusion, Exclusion, ModeCount }; diff --git a/core/rend/dx9/dx9_driver.h b/core/rend/dx9/dx9_driver.h index e1fe7527d..4e271b40d 100644 --- a/core/rend/dx9/dx9_driver.h +++ b/core/rend/dx9/dx9_driver.h @@ -97,7 +97,11 @@ public: texture.imTexture.d3dTexture = texture.tex.get(); texture.imTexture.pointSampling = nearestSampling; - return (ImTextureID)&texture; + return (ImTextureID)&texture.imTexture; + } + + void deleteTexture(const std::string& name) override { + textures.erase(name); } private: diff --git a/core/rend/gles/gldraw.cpp b/core/rend/gles/gldraw.cpp index a2384fe03..9108bfeae 100644 --- a/core/rend/gles/gldraw.cpp +++ b/core/rend/gles/gldraw.cpp @@ -790,7 +790,7 @@ bool OpenGLRenderer::renderLastFrame() -1.f, -1.f, 1.f, 0.f, 1.f, -1.f, 1.f, 1.f, 0.f, 0.f, 1.f, -1.f, 1.f, 1.f, 1.f, - 1.f, -1.f, 1.f, 1.f, 0.f, + 1.f, 1.f, 1.f, 1.f, 0.f, }; sverts[0] = sverts[5] = -1.f + gl.ofbo.shiftX * 2.f / framebuffer->getWidth(); sverts[10] = sverts[15] = sverts[0] + 2; @@ -817,6 +817,68 @@ bool OpenGLRenderer::renderLastFrame() return true; } +bool OpenGLRenderer::GetLastFrame(std::vector& data, int& width, int& height) +{ + GlFramebuffer *framebuffer = gl.ofbo2.ready ? gl.ofbo2.framebuffer.get() : gl.ofbo.framebuffer.get(); + if (framebuffer == nullptr) + return false; + width = framebuffer->getWidth(); + height = framebuffer->getHeight(); + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = gl.ofbo.aspectRatio * height; + if (width > w) + height = width / gl.ofbo.aspectRatio; + else + width = w; + + GlFramebuffer dstFramebuffer(width, height, false, false); + + glViewport(0, 0, width, height); + glcache.Disable(GL_BLEND); + verify(framebuffer->getTexture() != 0); + const float *vertices = nullptr; + if (config::Rotate90) + { + static float rvertices[4][5] = { + { -1.f, 1.f, 1.f, 1.f, 0.f }, + { -1.f, -1.f, 1.f, 1.f, 1.f }, + { 1.f, 1.f, 1.f, 0.f, 0.f }, + { 1.f, -1.f, 1.f, 0.f, 1.f }, + }; + vertices = &rvertices[0][0]; + } + drawQuad(framebuffer->getTexture(), config::Rotate90, false, vertices); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + data.resize(width * height * 3); + dstFramebuffer.bind(GL_READ_FRAMEBUFFER); + glPixelStorei(GL_PACK_ALIGNMENT, 1); + if (gl.is_gles) + { + // GL_RGB not supported + std::vector tmp(width * height * 4); + glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, tmp.data()); + u8 *dst = data.data(); + const u8 *src = tmp.data(); + while (src <= &tmp.back()) + { + *dst++ = *src++; + *dst++ = *src++; + *dst++ = *src++; + src++; + } + } + else { + glReadPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, data.data()); + } + restoreCurrentFramebuffer(); + glCheck(); + + return true; +} + #ifdef LIBRETRO #include "vmu_xhair.h" diff --git a/core/rend/gles/gles.cpp b/core/rend/gles/gles.cpp index 54117246c..aa88bf258 100644 --- a/core/rend/gles/gles.cpp +++ b/core/rend/gles/gles.cpp @@ -989,9 +989,11 @@ static void gl_create_resources() findGLVersion(); +#ifndef LIBRETRO if (gl.gl_major >= 3) // will be used later. Better fail fast verify(glGenVertexArrays != nullptr); +#endif //create vbos gl.vbo.geometry = std::make_unique(GL_ARRAY_BUFFER); @@ -1510,8 +1512,8 @@ bool OpenGLRenderer::Render() if (!config::EmulateFramebuffer) { - DrawOSD(false); frameRendered = true; + DrawOSD(false); renderVideoRouting(); } diff --git a/core/rend/gles/gles.h b/core/rend/gles/gles.h index 3063bd341..f7ebc131e 100755 --- a/core/rend/gles/gles.h +++ b/core/rend/gles/gles.h @@ -519,6 +519,7 @@ struct OpenGLRenderer : Renderer return ret; } + bool GetLastFrame(std::vector& data, int& width, int& height) override; void DrawOSD(bool clear_screen) override; @@ -570,7 +571,7 @@ protected: void initQuad(); void termQuad(); -void drawQuad(GLuint texId, bool rotate = false, bool swapY = false, float *coords = nullptr); +void drawQuad(GLuint texId, bool rotate = false, bool swapY = false, const float *coords = nullptr); extern const char* ShaderCompatSource; extern const char *VertexCompatShader; diff --git a/core/rend/gles/gltex.cpp b/core/rend/gles/gltex.cpp index 18af7462b..e71e8992f 100644 --- a/core/rend/gles/gltex.cpp +++ b/core/rend/gles/gltex.cpp @@ -315,28 +315,22 @@ void glReadFramebuffer(const FramebufferInfo& info) GLuint init_output_framebuffer(int width, int height) { if (gl.ofbo.framebuffer != nullptr - && (width != gl.ofbo.framebuffer->getWidth() || height != gl.ofbo.framebuffer->getHeight() - // if the rotate90 setting has changed - || (gl.gl_major >= 3 && (gl.ofbo.framebuffer->getTexture() == 0) == config::Rotate90))) + && (width != gl.ofbo.framebuffer->getWidth() || height != gl.ofbo.framebuffer->getHeight())) { gl.ofbo.framebuffer.reset(); } if (gl.ofbo.framebuffer == nullptr) { - GLuint texture = 0; - if (config::Rotate90) - { - // Create a texture for rendering to - texture = glcache.GenTexture(); - glcache.BindTexture(GL_TEXTURE_2D, texture); + // Create a texture for rendering to + GLuint texture = glcache.GenTexture(); + glcache.BindTexture(GL_TEXTURE_2D, texture); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - } + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); gl.ofbo.framebuffer = std::make_unique(width, height, true, texture); glcache.Disable(GL_SCISSOR_TEST); diff --git a/core/rend/gles/opengl_driver.cpp b/core/rend/gles/opengl_driver.cpp index 3d63a2570..425dfd778 100644 --- a/core/rend/gles/opengl_driver.cpp +++ b/core/rend/gles/opengl_driver.cpp @@ -69,6 +69,34 @@ OpenGLDriver::~OpenGLDriver() ImGui_ImplOpenGL3_Shutdown(); } +void OpenGLDriver::reset() +{ + ImGuiDriver::reset(); + for (auto& tex : vmu_lcd_tex_ids) + tex = ImTextureID{}; + vmuLastChanged.fill({}); +} + +void OpenGLDriver::updateVmuTextures() +{ + for (int i = 0; i < 8; i++) + { + if (!vmu_lcd_status[i]) + continue; + + if (this->vmuLastChanged[i] != ::vmuLastChanged[i] || vmu_lcd_tex_ids[i] == ImTextureID()) + { + try { + vmu_lcd_tex_ids[i] = updateTexture(":vmugl:" + std::to_string(i), (const u8 *)vmu_lcd_data[i], 48, 32, true); + } catch (...) { + continue; + } + if (vmu_lcd_tex_ids[i] != ImTextureID()) + this->vmuLastChanged[i] = ::vmuLastChanged[i]; + } + } +} + void OpenGLDriver::displayVmus() { if (!gameStarted) @@ -190,3 +218,12 @@ ImTextureID OpenGLDriver::updateTexture(const std::string& name, const u8 *data, return textures[name] = (ImTextureID)(u64)texId; } + +void OpenGLDriver::deleteTexture(const std::string& name) +{ + auto it = textures.find(name); + if (it != textures.end()) { + glcache.DeleteTextures(1, (GLuint *)&it->second); + textures.erase(it); + } +} diff --git a/core/rend/gles/opengl_driver.h b/core/rend/gles/opengl_driver.h index 59c41d146..d9262d541 100644 --- a/core/rend/gles/opengl_driver.h +++ b/core/rend/gles/opengl_driver.h @@ -33,6 +33,7 @@ public: void newFrame() override; void renderDrawData(ImDrawData* drawData, bool gui_open) override; void present() override; + void reset() override; void setFrameRendered() override { frameRendered = true; @@ -47,6 +48,7 @@ public: } ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) override; + void deleteTexture(const std::string& name) override; private: void emuEvent(Event event) @@ -66,8 +68,11 @@ private: static void emuEventCallback(Event event, void *p) { ((OpenGLDriver *)p)->emuEvent(event); } + void updateVmuTextures(); ImTextureID crosshairTexId = ImTextureID(); + ImTextureID vmu_lcd_tex_ids[8] {}; + std::array vmuLastChanged {}; bool gameStarted = false; bool frameRendered = false; std::unordered_map textures; diff --git a/core/rend/gles/quad.cpp b/core/rend/gles/quad.cpp index 5419b9340..c14c8f794 100644 --- a/core/rend/gles/quad.cpp +++ b/core/rend/gles/quad.cpp @@ -145,7 +145,7 @@ void termQuad() } // coords is an optional array of 20 floats (4 vertices with x,y,z,u,v each) -void drawQuad(GLuint texId, bool rotate, bool swapY, float *coords) +void drawQuad(GLuint texId, bool rotate, bool swapY, const float *coords) { glcache.Disable(GL_SCISSOR_TEST); glcache.Disable(GL_DEPTH_TEST); diff --git a/core/rend/vulkan/buffer.h b/core/rend/vulkan/buffer.h index 4f443a75e..0d56238ce 100644 --- a/core/rend/vulkan/buffer.h +++ b/core/rend/vulkan/buffer.h @@ -67,11 +67,11 @@ struct BufferData allocation.UnmapMemory(); } - void *MapMemory() + void *MapMemory() const { return allocation.MapMemory(); } - void UnmapMemory() + void UnmapMemory() const { allocation.UnmapMemory(); } diff --git a/core/rend/vulkan/texture.h b/core/rend/vulkan/texture.h index fc8ece958..9a1753f11 100644 --- a/core/rend/vulkan/texture.h +++ b/core/rend/vulkan/texture.h @@ -212,9 +212,14 @@ public: void Clear() { - BaseTextureCache::Clear(); + VulkanContext *context = VulkanContext::Instance(); for (auto& set : inFlightTextures) + { + for (Texture *tex : set) + tex->deferDeleteResource(context); set.clear(); + } + BaseTextureCache::Clear(); } private: diff --git a/core/rend/vulkan/vk_context_lr.h b/core/rend/vulkan/vk_context_lr.h index ddfeec52f..6202ec518 100644 --- a/core/rend/vulkan/vk_context_lr.h +++ b/core/rend/vulkan/vk_context_lr.h @@ -27,6 +27,7 @@ #include "wsi/context.h" #include "commandpool.h" #include "overlay.h" +#include static vk::Format findDepthFormat(vk::PhysicalDevice physicalDevice); @@ -43,6 +44,7 @@ public: u32 GetGraphicsQueueFamilyIndex() const { return retro_render_if->queue_index; } void PresentFrame(vk::Image image, vk::ImageView imageView, const vk::Extent2D& extent, float aspectRatio); + bool GetLastFrame(std::vector& data, int& width, int& height) { return false; } vk::PhysicalDevice GetPhysicalDevice() const { return physicalDevice; } vk::Device GetDevice() const { return device; } diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index e58252c71..6aede4e8e 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -966,6 +966,7 @@ void VulkanContext::PresentFrame(vk::Image image, vk::ImageView imageView, const try { NewFrame(); auto overlayCmdBuffer = PrepareOverlay(config::FloatVMUs, true); + gui_draw_osd(); BeginRenderPass(); @@ -973,6 +974,7 @@ void VulkanContext::PresentFrame(vk::Image image, vk::ImageView imageView, const DrawFrame(imageView, extent, aspectRatio); DrawOverlay(settings.display.uiScale, config::FloatVMUs, true); + imguiDriver->renderDrawData(ImGui::GetDrawData(), false); renderer->DrawOSD(false); EndFrame(overlayCmdBuffer); static_cast(renderer)->RenderVideoRouting(); @@ -1230,3 +1232,107 @@ VulkanContext::~VulkanContext() verify(contextInstance == this); contextInstance = nullptr; } + +bool VulkanContext::GetLastFrame(std::vector& data, int& width, int& height) +{ + if (!lastFrameView) + return false; + + width = lastFrameExtent.width; + height = lastFrameExtent.height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = lastFrameAR * height; + if (width > w) + height = width / lastFrameAR; + else + width = w; + // color attachment + FramebufferAttachment attachment(physicalDevice, *device); + attachment.Init(width, height, vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc, "screenshot"); + // command buffer + vk::UniqueCommandBuffer commandBuffer = std::move(device->allocateCommandBuffersUnique( + vk::CommandBufferAllocateInfo(*commandPools.back(), vk::CommandBufferLevel::ePrimary, 1)).front()); + commandBuffer->begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); + // render pass + vk::AttachmentDescription attachmentDescription = vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), vk::Format::eR8G8B8A8Unorm, vk::SampleCountFlagBits::e1, + vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, + vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferSrcOptimal); + vk::AttachmentReference colorReference(0, vk::ImageLayout::eColorAttachmentOptimal); + vk::SubpassDescription subpass(vk::SubpassDescriptionFlags(), vk::PipelineBindPoint::eGraphics, nullptr, colorReference, + nullptr, nullptr); + vk::UniqueRenderPass renderPass = device->createRenderPassUnique(vk::RenderPassCreateInfo(vk::RenderPassCreateFlags(), + attachmentDescription, subpass)); + // framebuffer + vk::ImageView imageView = attachment.GetImageView(); + vk::UniqueFramebuffer framebuffer = device->createFramebufferUnique(vk::FramebufferCreateInfo(vk::FramebufferCreateFlags(), + *renderPass, imageView, width, height, 1)); + vk::ClearValue clearValue; + commandBuffer->beginRenderPass(vk::RenderPassBeginInfo(*renderPass, *framebuffer, vk::Rect2D({0, 0}, {(u32)width, (u32)height}), clearValue), + vk::SubpassContents::eInline); + + // Pipeline + QuadPipeline pipeline(true, config::Rotate90); + pipeline.Init(shaderManager.get(), *renderPass, 0); + pipeline.BindPipeline(*commandBuffer); + + // Draw + QuadVertex vtx[] { + { -1, -1, 0, 0, 0 }, + { 1, -1, 0, 1, 0 }, + { -1, 1, 0, 0, 1 }, + { 1, 1, 0, 1, 1 }, + }; + + vk::Viewport viewport(0, 0, width, height); + commandBuffer->setViewport(0, viewport); + commandBuffer->setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), vk::Extent2D(width, height))); + QuadDrawer drawer; + drawer.Init(&pipeline); + drawer.Draw(*commandBuffer, lastFrameView, vtx, false); + commandBuffer->endRenderPass(); + + // Copy back + vk::BufferImageCopy copyRegion(0, width, height, vk::ImageSubresourceLayers(vk::ImageAspectFlagBits::eColor, 0, 0, 1), vk::Offset3D(0, 0, 0), + vk::Extent3D(width, height, 1)); + commandBuffer->copyImageToBuffer(attachment.GetImage(), vk::ImageLayout::eTransferSrcOptimal, + *attachment.GetBufferData()->buffer, copyRegion); + + vk::BufferMemoryBarrier bufferMemoryBarrier( + vk::AccessFlagBits::eTransferWrite, + vk::AccessFlagBits::eHostRead, + VK_QUEUE_FAMILY_IGNORED, + VK_QUEUE_FAMILY_IGNORED, + *attachment.GetBufferData()->buffer, + 0, + VK_WHOLE_SIZE); + commandBuffer->pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, + vk::PipelineStageFlagBits::eHost, {}, nullptr, bufferMemoryBarrier, nullptr); + commandBuffer->end(); + + vk::UniqueFence fence = device->createFenceUnique(vk::FenceCreateInfo()); + vk::SubmitInfo submitInfo(nullptr, nullptr, commandBuffer.get(), nullptr); + graphicsQueue.submit(submitInfo, *fence); + + vk::Result res = device->waitForFences(fence.get(), true, UINT64_MAX); + if (res != vk::Result::eSuccess) + WARN_LOG(RENDERER, "VulkanContext::GetLastFrame: waitForFences failed %d", (int)res); + + const u8 *img = (const u8 *)attachment.GetBufferData()->MapMemory(); + data.clear(); + data.reserve(width * height * 3); + for (int y = 0; y < height; y++) + { + for (int x = 0; x < width; x++) + { + data.push_back(*img++); + data.push_back(*img++); + data.push_back(*img++); + img++; + } + } + attachment.GetBufferData()->UnmapMemory(); + + return true; +} diff --git a/core/rend/vulkan/vulkan_context.h b/core/rend/vulkan/vulkan_context.h index f17fe4093..c2b000b49 100644 --- a/core/rend/vulkan/vulkan_context.h +++ b/core/rend/vulkan/vulkan_context.h @@ -38,6 +38,7 @@ public: #include "rend/TexCache.h" #include "overlay.h" #include "wsi/context.h" +#include struct ImDrawData; @@ -60,6 +61,7 @@ public: void Present() noexcept; void PresentFrame(vk::Image image, vk::ImageView imageView, const vk::Extent2D& extent, float aspectRatio) noexcept; void PresentLastFrame(); + bool GetLastFrame(std::vector& data, int& width, int& height); vk::PhysicalDevice GetPhysicalDevice() const { return physicalDevice; } vk::Device GetDevice() const { return *device; } diff --git a/core/rend/vulkan/vulkan_driver.h b/core/rend/vulkan/vulkan_driver.h index 0a8bc30cc..47b142853 100644 --- a/core/rend/vulkan/vulkan_driver.h +++ b/core/rend/vulkan/vulkan_driver.h @@ -133,6 +133,27 @@ public: return texId; } + void deleteTexture(const std::string& name) override + { + auto it = textures.find(name); + if (it != textures.end()) + { + class DescSetDeleter : public Deletable + { + public: + DescSetDeleter(VkDescriptorSet descSet) : descSet(descSet) {} + ~DescSetDeleter() { + ImGui_ImplVulkan_RemoveTexture(descSet); + } + VkDescriptorSet descSet; + }; + getContext()->addToFlight(new DescSetDeleter((VkDescriptorSet)it->second.textureId)); + if (it->second.texture != nullptr) + it->second.texture->deferDeleteResource(getContext()); + textures.erase(it); + } + } + private: struct VkTexture { VkTexture() = default; diff --git a/core/rend/vulkan/vulkan_renderer.cpp b/core/rend/vulkan/vulkan_renderer.cpp index 0586d997a..d32a70e0d 100644 --- a/core/rend/vulkan/vulkan_renderer.cpp +++ b/core/rend/vulkan/vulkan_renderer.cpp @@ -21,6 +21,281 @@ #include "vulkan.h" #include "vulkan_renderer.h" #include "drawer.h" +#include "hw/pvr/ta.h" +#include "rend/osd.h" +#include "rend/transform_matrix.h" + +bool BaseVulkanRenderer::BaseInit(vk::RenderPass renderPass, int subpass) +{ + texCommandPool.Init(); + fbCommandPool.Init(); + +#if defined(__ANDROID__) && !defined(LIBRETRO) + if (!vjoyTexture) + { + int w, h; + u8 *image_data = loadOSDButtons(w, h); + texCommandPool.BeginFrame(); + vjoyTexture = std::make_unique(); + vjoyTexture->tex_type = TextureType::_8888; + vk::CommandBuffer cmdBuffer = texCommandPool.Allocate(); + cmdBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); + vjoyTexture->SetCommandBuffer(cmdBuffer); + vjoyTexture->UploadToGPU(w, h, image_data, false); + vjoyTexture->SetCommandBuffer(nullptr); + cmdBuffer.end(); + texCommandPool.EndFrame(); + delete [] image_data; + osdPipeline.Init(&shaderManager, vjoyTexture->GetImageView(), GetContext()->GetRenderPass()); + } + if (!osdBuffer) + { + osdBuffer = std::make_unique(sizeof(OSDVertex) * VJOY_VISIBLE * 4, + vk::BufferUsageFlagBits::eVertexBuffer); + } +#endif + quadPipeline = std::make_unique(false, false); + quadPipeline->Init(&shaderManager, renderPass, subpass); + framebufferDrawer = std::make_unique(); + framebufferDrawer->Init(quadPipeline.get()); + + return true; +} + +void BaseVulkanRenderer::Term() +{ + GetContext()->WaitIdle(); + GetContext()->PresentFrame(nullptr, nullptr, vk::Extent2D(), 0); +#if defined(VIDEO_ROUTING) && defined(TARGET_MAC) + os_VideoRoutingTermVk(); +#endif + framebufferDrawer.reset(); + quadPipeline.reset(); + osdBuffer.reset(); + osdPipeline.Term(); + vjoyTexture.reset(); + textureCache.Clear(); + fogTexture = nullptr; + paletteTexture = nullptr; + texCommandPool.Term(); + fbCommandPool.Term(); + framebufferTextures.clear(); + framebufferTexIndex = 0; + shaderManager.term(); +} + +BaseTextureCacheData *BaseVulkanRenderer::GetTexture(TSP tsp, TCW tcw) +{ + Texture* tf = textureCache.getTextureCacheData(tsp, tcw); + + //update if needed + if (tf->NeedsUpdate()) + { + // This kills performance when a frame is skipped and lots of texture updated each frame + //if (textureCache.IsInFlight(tf, true)) + // textureCache.DestroyLater(tf); + tf->SetCommandBuffer(texCommandBuffer); + if (!tf->Update()) + { + tf->SetCommandBuffer(nullptr); + return nullptr; + } + } + else if (tf->IsCustomTextureAvailable()) + { + tf->deferDeleteResource(&texCommandPool); + tf->SetCommandBuffer(texCommandBuffer); + tf->CheckCustomTexture(); + } + tf->SetCommandBuffer(nullptr); + textureCache.SetInFlight(tf); + + return tf; +} + +void BaseVulkanRenderer::Process(TA_context* ctx) +{ + if (KillTex) + textureCache.Clear(); + + texCommandPool.BeginFrame(); + textureCache.SetCurrentIndex(texCommandPool.GetIndex()); + textureCache.Cleanup(); + + texCommandBuffer = texCommandPool.Allocate(); + texCommandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); + + ta_parse(ctx, true); + + CheckFogTexture(); + CheckPaletteTexture(); + texCommandBuffer.end(); +} + +void BaseVulkanRenderer::ReInitOSD() +{ + texCommandPool.Init(); + fbCommandPool.Init(); +#if defined(__ANDROID__) && !defined(LIBRETRO) + osdPipeline.Init(&shaderManager, vjoyTexture->GetImageView(), GetContext()->GetRenderPass()); +#endif +} + +void BaseVulkanRenderer::DrawOSD(bool clear_screen) +{ +#ifndef LIBRETRO + if (!vjoyTexture) + return; + try { + if (clear_screen) + { + GetContext()->NewFrame(); + GetContext()->BeginRenderPass(); + GetContext()->PresentLastFrame(); + } + const float dc2s_scale_h = settings.display.height / 480.0f; + const float sidebarWidth = (settings.display.width - dc2s_scale_h * 640.0f) / 2; + + std::vector osdVertices = GetOSDVertices(); + const float x1 = 2.0f / (settings.display.width / dc2s_scale_h); + const float y1 = 2.0f / 480; + const float x2 = 1 - 2 * sidebarWidth / settings.display.width; + const float y2 = 1; + for (OSDVertex& vtx : osdVertices) + { + vtx.x = vtx.x * x1 - x2; + vtx.y = vtx.y * y1 - y2; + } + + const vk::CommandBuffer cmdBuffer = GetContext()->GetCurrentCommandBuffer(); + cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, osdPipeline.GetPipeline()); + + osdPipeline.BindDescriptorSets(cmdBuffer); + const vk::Viewport viewport(0, 0, (float)settings.display.width, (float)settings.display.height, 0, 1.f); + cmdBuffer.setViewport(0, viewport); + const vk::Rect2D scissor({ 0, 0 }, { (u32)settings.display.width, (u32)settings.display.height }); + cmdBuffer.setScissor(0, scissor); + osdBuffer->upload((u32)(osdVertices.size() * sizeof(OSDVertex)), osdVertices.data()); + cmdBuffer.bindVertexBuffers(0, osdBuffer->buffer.get(), {0}); + for (u32 i = 0; i < (u32)osdVertices.size(); i += 4) + cmdBuffer.draw(4, 1, i, 0); + if (clear_screen) + GetContext()->EndFrame(); + } catch (const InvalidVulkanContext&) { + } +#endif +} + +void BaseVulkanRenderer::RenderFramebuffer(const FramebufferInfo& info) +{ + framebufferTexIndex = (framebufferTexIndex + 1) % GetContext()->GetSwapChainSize(); + + if (framebufferTextures.size() != GetContext()->GetSwapChainSize()) + framebufferTextures.resize(GetContext()->GetSwapChainSize()); + std::unique_ptr& curTexture = framebufferTextures[framebufferTexIndex]; + if (!curTexture) + { + curTexture = std::make_unique(); + curTexture->tex_type = TextureType::_8888; + } + + fbCommandPool.BeginFrame(); + vk::CommandBuffer commandBuffer = fbCommandPool.Allocate(); + commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); + curTexture->SetCommandBuffer(commandBuffer); + + if (info.fb_r_ctrl.fb_enable == 0 || info.vo_control.blank_video == 1) + { + // Video output disabled + u8 rgba[] { (u8)info.vo_border_col._red, (u8)info.vo_border_col._green, (u8)info.vo_border_col._blue, 255 }; + curTexture->UploadToGPU(1, 1, rgba, false); + } + else + { + PixelBuffer pb; + int width; + int height; + ReadFramebuffer(info, pb, width, height); + + curTexture->UploadToGPU(width, height, (u8*)pb.data(), false); + } + + curTexture->SetCommandBuffer(nullptr); + commandBuffer.end(); + fbCommandPool.EndFrame(); + framebufferRendered = true; +} + +void BaseVulkanRenderer::RenderVideoRouting() +{ +#if defined(VIDEO_ROUTING) && defined(TARGET_MAC) + if (config::VideoRouting) + { + auto device = GetContext()->GetDevice(); + auto srcImage = device.getSwapchainImagesKHR(GetContext()->GetSwapChain())[GetContext()->GetCurrentImageIndex()]; + auto graphicsQueue = device.getQueue(GetContext()->GetGraphicsQueueFamilyIndex(), 0); + + int targetWidth = (config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width); + int targetHeight = (config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height); + + extern void os_VideoRoutingPublishFrameTexture(const vk::Device& device, const vk::Image& image, const vk::Queue& queue, float x, float y, float w, float h); + os_VideoRoutingPublishFrameTexture(device, srcImage, graphicsQueue, 0, 0, targetWidth, targetHeight); + } + else + { + os_VideoRoutingTermVk(); + } +#endif +} + +void BaseVulkanRenderer::CheckFogTexture() +{ + if (!fogTexture) + { + fogTexture = std::make_unique(); + fogTexture->tex_type = TextureType::_8; + fog_needs_update = true; + } + if (!fog_needs_update || !config::Fog) + return; + fog_needs_update = false; + u8 texData[256]; + MakeFogTexture(texData); + + fogTexture->SetCommandBuffer(texCommandBuffer); + fogTexture->UploadToGPU(128, 2, texData, false); + fogTexture->SetCommandBuffer(nullptr); +} + +void BaseVulkanRenderer::CheckPaletteTexture() +{ + if (!paletteTexture) + { + paletteTexture = std::make_unique(); + paletteTexture->tex_type = TextureType::_8888; + palette_updated = true; + } + if (!palette_updated) + return; + palette_updated = false; + + paletteTexture->SetCommandBuffer(texCommandBuffer); + paletteTexture->UploadToGPU(1024, 1, (u8 *)palette32_ram, false); + paletteTexture->SetCommandBuffer(nullptr); +} + +bool BaseVulkanRenderer::presentFramebuffer() +{ + if (framebufferTexIndex >= (int)framebufferTextures.size()) + return false; + Texture *fbTexture = framebufferTextures[framebufferTexIndex].get(); + if (fbTexture == nullptr) + return false; + GetContext()->PresentFrame(fbTexture->GetImage(), fbTexture->GetImageView(), fbTexture->getSize(), + getDCFramebufferAspectRatio()); + framebufferRendered = false; + return true; +} class VulkanRenderer final : public BaseVulkanRenderer { diff --git a/core/rend/vulkan/vulkan_renderer.h b/core/rend/vulkan/vulkan_renderer.h index f31b2328f..f22f1230d 100644 --- a/core/rend/vulkan/vulkan_renderer.h +++ b/core/rend/vulkan/vulkan_renderer.h @@ -19,14 +19,9 @@ #pragma once #include "vulkan.h" #include "hw/pvr/Renderer_if.h" -#include "hw/pvr/ta.h" #include "commandpool.h" #include "pipeline.h" -#include "rend/osd.h" -#include "rend/transform_matrix.h" -#ifndef LIBRETRO -#include "ui/gui.h" -#endif +#include "shaders.h" #include #include @@ -36,232 +31,21 @@ void os_VideoRoutingTermVk(); class BaseVulkanRenderer : public Renderer { protected: - bool BaseInit(vk::RenderPass renderPass, int subpass = 0) - { - texCommandPool.Init(); - fbCommandPool.Init(); - -#if defined(__ANDROID__) && !defined(LIBRETRO) - if (!vjoyTexture) - { - int w, h; - u8 *image_data = loadOSDButtons(w, h); - texCommandPool.BeginFrame(); - vjoyTexture = std::make_unique(); - vjoyTexture->tex_type = TextureType::_8888; - vk::CommandBuffer cmdBuffer = texCommandPool.Allocate(); - cmdBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - vjoyTexture->SetCommandBuffer(cmdBuffer); - vjoyTexture->UploadToGPU(w, h, image_data, false); - vjoyTexture->SetCommandBuffer(nullptr); - cmdBuffer.end(); - texCommandPool.EndFrame(); - delete [] image_data; - osdPipeline.Init(&shaderManager, vjoyTexture->GetImageView(), GetContext()->GetRenderPass()); - } - if (!osdBuffer) - { - osdBuffer = std::make_unique(sizeof(OSDVertex) * VJOY_VISIBLE * 4, - vk::BufferUsageFlagBits::eVertexBuffer); - } -#endif - quadPipeline = std::make_unique(false, false); - quadPipeline->Init(&shaderManager, renderPass, subpass); - framebufferDrawer = std::make_unique(); - framebufferDrawer->Init(quadPipeline.get()); - - return true; - } + bool BaseInit(vk::RenderPass renderPass, int subpass = 0); public: - void Term() override - { - GetContext()->WaitIdle(); - GetContext()->PresentFrame(nullptr, nullptr, vk::Extent2D(), 0); -#if defined(VIDEO_ROUTING) && defined(TARGET_MAC) - os_VideoRoutingTermVk(); -#endif - framebufferDrawer.reset(); - quadPipeline.reset(); - osdBuffer.reset(); - osdPipeline.Term(); - vjoyTexture.reset(); - textureCache.Clear(); - fogTexture = nullptr; - paletteTexture = nullptr; - texCommandPool.Term(); - fbCommandPool.Term(); - framebufferTextures.clear(); - framebufferTexIndex = 0; - shaderManager.term(); + void Term() override; + BaseTextureCacheData *GetTexture(TSP tsp, TCW tcw) override; + void Process(TA_context* ctx) override; + void ReInitOSD(); + void DrawOSD(bool clear_screen) override; + void RenderFramebuffer(const FramebufferInfo& info) override; + void RenderVideoRouting(); + + bool GetLastFrame(std::vector& data, int& width, int& height) override { + return GetContext()->GetLastFrame(data, width, height); } - BaseTextureCacheData *GetTexture(TSP tsp, TCW tcw) override - { - Texture* tf = textureCache.getTextureCacheData(tsp, tcw); - - //update if needed - if (tf->NeedsUpdate()) - { - // This kills performance when a frame is skipped and lots of texture updated each frame - //if (textureCache.IsInFlight(tf, true)) - // textureCache.DestroyLater(tf); - tf->SetCommandBuffer(texCommandBuffer); - if (!tf->Update()) - { - tf->SetCommandBuffer(nullptr); - return nullptr; - } - } - else if (tf->IsCustomTextureAvailable()) - { - tf->deferDeleteResource(&texCommandPool); - tf->SetCommandBuffer(texCommandBuffer); - tf->CheckCustomTexture(); - } - tf->SetCommandBuffer(nullptr); - textureCache.SetInFlight(tf); - - return tf; - } - - void Process(TA_context* ctx) override - { - if (KillTex) - textureCache.Clear(); - - texCommandPool.BeginFrame(); - textureCache.SetCurrentIndex(texCommandPool.GetIndex()); - textureCache.Cleanup(); - - texCommandBuffer = texCommandPool.Allocate(); - texCommandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - - ta_parse(ctx, true); - - CheckFogTexture(); - CheckPaletteTexture(); - texCommandBuffer.end(); - } - - void ReInitOSD() - { - texCommandPool.Init(); - fbCommandPool.Init(); -#if defined(__ANDROID__) && !defined(LIBRETRO) - osdPipeline.Init(&shaderManager, vjoyTexture->GetImageView(), GetContext()->GetRenderPass()); -#endif - } - - void DrawOSD(bool clear_screen) override - { -#ifndef LIBRETRO - gui_display_osd(); - if (!vjoyTexture) - return; - try { - if (clear_screen) - { - GetContext()->NewFrame(); - GetContext()->BeginRenderPass(); - GetContext()->PresentLastFrame(); - } - const float dc2s_scale_h = settings.display.height / 480.0f; - const float sidebarWidth = (settings.display.width - dc2s_scale_h * 640.0f) / 2; - - std::vector osdVertices = GetOSDVertices(); - const float x1 = 2.0f / (settings.display.width / dc2s_scale_h); - const float y1 = 2.0f / 480; - const float x2 = 1 - 2 * sidebarWidth / settings.display.width; - const float y2 = 1; - for (OSDVertex& vtx : osdVertices) - { - vtx.x = vtx.x * x1 - x2; - vtx.y = vtx.y * y1 - y2; - } - - const vk::CommandBuffer cmdBuffer = GetContext()->GetCurrentCommandBuffer(); - cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, osdPipeline.GetPipeline()); - - osdPipeline.BindDescriptorSets(cmdBuffer); - const vk::Viewport viewport(0, 0, (float)settings.display.width, (float)settings.display.height, 0, 1.f); - cmdBuffer.setViewport(0, viewport); - const vk::Rect2D scissor({ 0, 0 }, { (u32)settings.display.width, (u32)settings.display.height }); - cmdBuffer.setScissor(0, scissor); - osdBuffer->upload((u32)(osdVertices.size() * sizeof(OSDVertex)), osdVertices.data()); - cmdBuffer.bindVertexBuffers(0, osdBuffer->buffer.get(), {0}); - for (u32 i = 0; i < (u32)osdVertices.size(); i += 4) - cmdBuffer.draw(4, 1, i, 0); - if (clear_screen) - GetContext()->EndFrame(); - } catch (const InvalidVulkanContext&) { - } -#endif - } - - void RenderFramebuffer(const FramebufferInfo& info) override - { - framebufferTexIndex = (framebufferTexIndex + 1) % GetContext()->GetSwapChainSize(); - - if (framebufferTextures.size() != GetContext()->GetSwapChainSize()) - framebufferTextures.resize(GetContext()->GetSwapChainSize()); - std::unique_ptr& curTexture = framebufferTextures[framebufferTexIndex]; - if (!curTexture) - { - curTexture = std::make_unique(); - curTexture->tex_type = TextureType::_8888; - } - - fbCommandPool.BeginFrame(); - vk::CommandBuffer commandBuffer = fbCommandPool.Allocate(); - commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - curTexture->SetCommandBuffer(commandBuffer); - - if (info.fb_r_ctrl.fb_enable == 0 || info.vo_control.blank_video == 1) - { - // Video output disabled - u8 rgba[] { (u8)info.vo_border_col._red, (u8)info.vo_border_col._green, (u8)info.vo_border_col._blue, 255 }; - curTexture->UploadToGPU(1, 1, rgba, false); - } - else - { - PixelBuffer pb; - int width; - int height; - ReadFramebuffer(info, pb, width, height); - - curTexture->UploadToGPU(width, height, (u8*)pb.data(), false); - } - - curTexture->SetCommandBuffer(nullptr); - commandBuffer.end(); - fbCommandPool.EndFrame(); - framebufferRendered = true; - } - - void RenderVideoRouting() - { -#if defined(VIDEO_ROUTING) && defined(TARGET_MAC) - if (config::VideoRouting) - { - auto device = GetContext()->GetDevice(); - auto srcImage = device.getSwapchainImagesKHR(GetContext()->GetSwapChain())[GetContext()->GetCurrentImageIndex()]; - auto graphicsQueue = device.getQueue(GetContext()->GetGraphicsQueueFamilyIndex(), 0); - - int targetWidth = (config::VideoRoutingScale ? config::VideoRoutingVRes * settings.display.width / settings.display.height : settings.display.width); - int targetHeight = (config::VideoRoutingScale ? config::VideoRoutingVRes : settings.display.height); - - extern void os_VideoRoutingPublishFrameTexture(const vk::Device& device, const vk::Image& image, const vk::Queue& queue, float x, float y, float w, float h); - os_VideoRoutingPublishFrameTexture(device, srcImage, graphicsQueue, 0, 0, targetWidth, targetHeight); - } - else - { - os_VideoRoutingTermVk(); - } -#endif - } - - protected: BaseVulkanRenderer() : viewport(640, 480) {} @@ -273,57 +57,9 @@ protected: viewport.height = h; } - void CheckFogTexture() - { - if (!fogTexture) - { - fogTexture = std::make_unique(); - fogTexture->tex_type = TextureType::_8; - fog_needs_update = true; - } - if (!fog_needs_update || !config::Fog) - return; - fog_needs_update = false; - u8 texData[256]; - MakeFogTexture(texData); - fogTexture->SetCommandBuffer(texCommandBuffer); - - fogTexture->UploadToGPU(128, 2, texData, false); - - fogTexture->SetCommandBuffer(nullptr); - } - - void CheckPaletteTexture() - { - if (!paletteTexture) - { - paletteTexture = std::make_unique(); - paletteTexture->tex_type = TextureType::_8888; - forcePaletteUpdate(); - } - if (!palette_updated) - return; - palette_updated = false; - - paletteTexture->SetCommandBuffer(texCommandBuffer); - - paletteTexture->UploadToGPU(1024, 1, (u8 *)palette32_ram, false); - - paletteTexture->SetCommandBuffer(nullptr); - } - - bool presentFramebuffer() - { - if (framebufferTexIndex >= (int)framebufferTextures.size()) - return false; - Texture *fbTexture = framebufferTextures[framebufferTexIndex].get(); - if (fbTexture == nullptr) - return false; - GetContext()->PresentFrame(fbTexture->GetImage(), fbTexture->GetImageView(), fbTexture->getSize(), - getDCFramebufferAspectRatio()); - framebufferRendered = false; - return true; - } + void CheckFogTexture(); + void CheckPaletteTexture(); + bool presentFramebuffer(); ShaderManager shaderManager; std::unique_ptr fogTexture; diff --git a/core/serialize.cpp b/core/serialize.cpp index 9094e164b..87bc2c44c 100644 --- a/core/serialize.cpp +++ b/core/serialize.cpp @@ -99,3 +99,59 @@ void dc_deserialize(Deserializer& deser) DEBUG_LOG(SAVESTATE, "Loaded %d bytes", (u32)deser.size()); } + +Deserializer::Deserializer(const void *data, size_t limit, bool rollback) + : SerializeBase(limit, rollback), data((const u8 *)data) +{ + if (!memcmp(data, "RASTATE\001", 8)) + { + // RetroArch savestates now have several sections: MEM, ACHV, RPLY, etc. + const u8 *p = this->data + 8; + limit -= 8; + while (limit > 8) + { + const u8 *section = p; + u32 sectionSize = *(const u32 *)&p[4]; + p += 8; + limit -= 8; + if (!memcmp(section, "MEM ", 4)) + { + // That's the part we're interested in + this->data = p; + this->limit = sectionSize; + break; + } + sectionSize = (sectionSize + 7) & ~7; // align to 8 bytes + if (limit < sectionSize) { + limit = 0; + break; + } + p += sectionSize; + limit -= sectionSize; + } + if (limit <= 8) + throw Exception("Can't find MEM section in RetroArch savestate"); + } + deserialize(_version); + if (_version < V16) + throw Exception("Unsupported version"); + if (_version > Current) + throw Exception("Version too recent"); + + if(_version >= V42 && settings.platform.isConsole()) + { + u32 ramSize; + deserialize(ramSize); + if (ramSize != settings.platform.ram_size) + throw Exception("Selected RAM Size doesn't match Save State"); + } +} + +Serializer::Serializer(void *data, size_t limit, bool rollback) + : SerializeBase(limit, rollback), data((u8 *)data) +{ + Version v = Current; + serialize(v); + if (settings.platform.isConsole()) + serialize(settings.platform.ram_size); +} diff --git a/core/serialize.h b/core/serialize.h index 2c9ebb813..5650122d0 100644 --- a/core/serialize.h +++ b/core/serialize.h @@ -87,29 +87,7 @@ public: Exception(const char *msg) : std::runtime_error(msg) {} }; - Deserializer(const void *data, size_t limit, bool rollback = false) - : SerializeBase(limit, rollback), data((const u8 *)data) - { - if (!memcmp(data, "RASTATE\001", 8)) - { - // RetroArch savestate: a 16-byte header is now added here because why not? - this->data += 16; - this->limit -= 16; - } - deserialize(_version); - if (_version < V16) - throw Exception("Unsupported version"); - if (_version > Current) - throw Exception("Version too recent"); - - if(_version >= V42 && settings.platform.isConsole()) - { - u32 ramSize; - deserialize(ramSize); - if (ramSize != settings.platform.ram_size) - throw Exception("Selected RAM Size doesn't match Save State"); - } - } + Deserializer(const void *data, size_t limit, bool rollback = false); template void deserialize(T& obj) @@ -165,14 +143,7 @@ public: Serializer() : Serializer(nullptr, std::numeric_limits::max(), false) {} - Serializer(void *data, size_t limit, bool rollback = false) - : SerializeBase(limit, rollback), data((u8 *)data) - { - Version v = Current; - serialize(v); - if (settings.platform.isConsole()) - serialize(settings.platform.ram_size); - } + Serializer(void *data, size_t limit, bool rollback = false); template void serialize(const T& obj) diff --git a/core/stdclass.h b/core/stdclass.h index d5efa9e82..6c7d209fd 100644 --- a/core/stdclass.h +++ b/core/stdclass.h @@ -9,6 +9,8 @@ #include #include #include +#include +#include #ifdef __ANDROID__ #include @@ -199,3 +201,39 @@ public: }; u64 getTimeMs(); + +class ThreadRunner +{ +public: + void init() { + threadId = std::this_thread::get_id(); + } + void runOnThread(std::function func) + { + if (threadId == std::this_thread::get_id()) { + func(); + } + else { + LockGuard _(mutex); + tasks.push_back(func); + } + } + void execTasks() + { + assert(threadId == std::this_thread::get_id()); + std::vector> localTasks; + { + LockGuard _(mutex); + std::swap(localTasks, tasks); + } + for (auto& func : localTasks) + func(); + } + +private: + using LockGuard = std::lock_guard; + + std::thread::id threadId; + std::vector> tasks; + std::mutex mutex; +}; diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 3860735c2..b448dfa1f 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -50,6 +50,8 @@ #include "gui_achievements.h" #include "IconsFontAwesome6.h" #include "oslib/storage.h" +#include +#include "hw/pvr/Renderer_if.h" #if defined(USE_SDL) #include "sdl/sdl.h" #endif @@ -101,6 +103,7 @@ using LockGuard = std::lock_guard; ImFont *largeFont; static Toast toast; +static ThreadRunner uiThreadRunner; static void emuEventCallback(Event event, void *) { @@ -190,12 +193,13 @@ void gui_initFonts() static float uiScale; verify(inited); + uiThreadRunner.init(); #if !defined(TARGET_UWP) && !defined(__SWITCH__) settings.display.uiScale = std::max(1.f, settings.display.dpi / 100.f * 0.75f); // Limit scaling on small low-res screens if (settings.display.width <= 640 || settings.display.height <= 480) - settings.display.uiScale = std::min(1.4f, settings.display.uiScale); + settings.display.uiScale = std::min(1.2f, settings.display.uiScale); #endif settings.display.uiScale *= config::UIScaling / 100.f; if (settings.display.uiScale == uiScale && ImGui::GetIO().Fonts->IsBuilt()) @@ -470,7 +474,6 @@ static void delayedKeysUp() static void gui_endFrame(bool gui_open) { - ImGui::Render(); imguiDriver->renderDrawData(ImGui::GetDrawData(), gui_open); delayedKeysUp(); } @@ -581,6 +584,50 @@ static bool savestateAllowed() return !settings.content.path.empty() && !settings.network.online && !settings.naomi.multiboard; } +static void appendVectorData(void *context, void *data, int size) +{ + std::vector& v = *(std::vector *)context; + const u8 *bytes = (const u8 *)data; + v.insert(v.end(), bytes, bytes + size); +} + +static void getScreenshot(std::vector& data) +{ + data.clear(); + std::vector rawData; + int width, height; + if (renderer == nullptr || !renderer->GetLastFrame(rawData, width, height)) + return; + stbi_flip_vertically_on_write(0); + stbi_write_png_to_func(appendVectorData, &data, width, height, 3, &rawData[0], 0); +} + +#ifdef _WIN32 +static struct tm *localtime_r(const time_t *_clock, struct tm *_result) +{ + return localtime_s(_result, _clock) ? nullptr : _result; +} +#endif + +static std::string timeToString(time_t time) +{ + tm t; + if (localtime_r(&time, &t) == nullptr) + return {}; + std::string s(32, '\0'); + s.resize(snprintf(s.data(), 32, "%04d/%02d/%02d %02d:%02d:%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec)); + return s; +} + +static void savestate() +{ + std::vector pngData; + getScreenshot(pngData); + dc_savestate(config::SavestateSlot, pngData.empty() ? nullptr : &pngData[0], pngData.size()); + ImguiStateTexture savestatePic; + savestatePic.invalidate(); +} + static void gui_display_commands() { fullScreenWindow(false); @@ -595,33 +642,38 @@ static void gui_display_commands() (ImGui::GetContentRegionAvail().x - uiScaled(100 + 150) - ImGui::GetStyle().FramePadding.x * 2) / 2 / uiScaled(1)); float buttonWidth = 150.f; // not scaled - bool lowW = ImGui::GetContentRegionAvail().x < (uiScaled(100 + buttonWidth * 3) - + ImGui::GetStyle().FramePadding.x * 2 + ImGui::GetStyle().ItemSpacing.x * 2); - if (lowW) + bool lowWidth = ImGui::GetContentRegionAvail().x < uiScaled(100 + buttonWidth * 3) + + ImGui::GetStyle().FramePadding.x * 2 + ImGui::GetStyle().ItemSpacing.x * 2; + if (lowWidth) buttonWidth = std::min(150.f, (ImGui::GetContentRegionAvail().x - ImGui::GetStyle().FramePadding.x * 2 - ImGui::GetStyle().ItemSpacing.x * 2) / 3 / uiScaled(1)); + bool lowHeight = ImGui::GetContentRegionAvail().y < uiScaled(100 + 50 * 2 + buttonWidth * 3 / 4) + ImGui::GetTextLineHeightWithSpacing() * 2 + + ImGui::GetStyle().ItemSpacing.y * 2 + ImGui::GetStyle().WindowPadding.y; GameMedia game; game.path = settings.content.path; game.fileName = settings.content.fileName; GameBoxart art = boxart.getBoxart(game); - ImguiTexture tex(art.boxartPath); + ImguiFileTexture tex(art.boxartPath); // TODO use placeholder image if not available tex.draw(ScaledVec2(100, 100)); ImGui::SameLine(); - ImGui::BeginChild("game_info", ScaledVec2(0, 100.f), ImGuiChildFlags_Border, ImGuiWindowFlags_None); - ImGui::PushFont(largeFont); - ImGui::Text("%s", art.name.c_str()); - ImGui::PopFont(); + if (!lowHeight) { - ImguiStyleColor _(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); - ImGui::TextWrapped("%s", art.fileName.c_str()); + ImGui::BeginChild("game_info", ScaledVec2(0, 100.f), ImGuiChildFlags_Border, ImGuiWindowFlags_None); + ImGui::PushFont(largeFont); + ImGui::Text("%s", art.name.c_str()); + ImGui::PopFont(); + { + ImguiStyleColor _(ImGuiCol_Text, ImVec4(0.75f, 0.75f, 0.75f, 1.f)); + ImGui::TextWrapped("%s", art.fileName.c_str()); + } + ImGui::EndChild(); } - ImGui::EndChild(); - if (lowW) { + if (lowWidth) { ImGui::Columns(3, "buttons", false); } else @@ -632,7 +684,7 @@ static void gui_display_commands() ImGui::SetColumnWidth(2, uiScaled(columnWidth)); const ImVec2 vmuPos = ImGui::GetStyle().WindowPadding + ScaledVec2(0.f, 100.f) + ImVec2(insetLeft, ImGui::GetStyle().ItemSpacing.y); - imguiDriver->displayVmus(vmuPos); + ImguiVmuTexture::displayVmus(vmuPos); ImGui::NextColumn(); } ImguiStyleVar _1{ImGuiStyleVar_FramePadding, ScaledVec2(12.f, 3.f)}; @@ -657,6 +709,17 @@ static void gui_display_commands() if (ImGui::Button(ICON_FA_TROPHY " Achievements", ScaledVec2(buttonWidth, 50)) && achievements::isActive()) gui_setState(GuiState::Achievements); } + // Barcode + if (card_reader::barcodeAvailable()) + { + ImGui::Text("Barcode Card"); + char cardBuf[64] {}; + strncpy(cardBuf, card_reader::barcodeGetCard().c_str(), sizeof(cardBuf) - 1); + ImGui::SetNextItemWidth(uiScaled(buttonWidth)); + if (ImGui::InputText("##barcode", cardBuf, sizeof(cardBuf), ImGuiInputTextFlags_None, nullptr, nullptr)) + card_reader::barcodeSetCard(cardBuf); + } + ImGui::NextColumn(); // Insert/Eject Disk @@ -674,6 +737,7 @@ static void gui_display_commands() // Settings if (ImGui::Button(ICON_FA_GEAR " Settings", ScaledVec2(buttonWidth, 50))) gui_setState(GuiState::Settings); + // Exit if (ImGui::Button(commandLineStart ? ICON_FA_POWER_OFF " Exit" : ICON_FA_POWER_OFF " Close Game", ScaledVec2(buttonWidth, 50))) gui_stop_game(); @@ -681,10 +745,12 @@ static void gui_display_commands() ImGui::NextColumn(); { DisabledScope _{!savestateAllowed()}; + ImguiStateTexture savestatePic; + time_t savestateDate = dc_getStateCreationDate(config::SavestateSlot); + // Load State { - DisabledScope _{settings.raHardcoreMode}; - // Load State + DisabledScope _{settings.raHardcoreMode || savestateDate == 0}; if (ImGui::Button(ICON_FA_CLOCK_ROTATE_LEFT " Load State", ScaledVec2(buttonWidth, 50)) && savestateAllowed()) { gui_setState(GuiState::Closed); @@ -696,7 +762,7 @@ static void gui_display_commands() if (ImGui::Button(ICON_FA_DOWNLOAD " Save State", ScaledVec2(buttonWidth, 50)) && savestateAllowed()) { gui_setState(GuiState::Closed); - dc_savestate(config::SavestateSlot); + savestate(); } { @@ -720,30 +786,20 @@ static void gui_display_commands() ImGui::SameLine(0, spacingW); if (ImGui::ArrowButton("##next-slot", ImGuiDir_Right)) { - if (config::SavestateSlot == 9) - config::SavestateSlot = 0; - else + if (config::SavestateSlot == 9) + config::SavestateSlot = 0; + else config::SavestateSlot++; - SaveSettings(); + SaveSettings(); } - std::string date = dc_getStateUpdateDate(config::SavestateSlot); { ImVec4 gray(0.75f, 0.75f, 0.75f, 1.f); - if (date.empty()) + if (savestateDate == 0) ImGui::TextColored(gray, "Empty"); else - ImGui::TextColored(gray, "%s", date.c_str()); + ImGui::TextColored(gray, "%s", timeToString(savestateDate).c_str()); } - } - // Barcode - if (card_reader::barcodeAvailable()) - { - ImGui::NewLine(); - ImGui::Text("Barcode Card"); - char cardBuf[64] {}; - strncpy(cardBuf, card_reader::barcodeGetCard().c_str(), sizeof(cardBuf) - 1); - if (ImGui::InputText("##barcode", cardBuf, sizeof(cardBuf), ImGuiInputTextFlags_None, nullptr, nullptr)) - card_reader::barcodeSetCard(cardBuf); + savestatePic.draw(ScaledVec2(buttonWidth, 0.f)); } ImGui::Columns(1, nullptr, false); @@ -929,6 +985,7 @@ const Mapping dcButtons[] = { { EMU_BTN_LOADSTATE, "Load State" }, { EMU_BTN_SAVESTATE, "Save State" }, { EMU_BTN_BYPASS_KB, "Bypass Emulated Keyboard" }, + { EMU_BTN_SCREENSHOT, "Save Screenshot" }, { EMU_BTN_NONE, nullptr } }; @@ -982,6 +1039,7 @@ const Mapping arcadeButtons[] = { { EMU_BTN_LOADSTATE, "Load State" }, { EMU_BTN_SAVESTATE, "Save State" }, { EMU_BTN_BYPASS_KB, "Bypass Emulated Keyboard" }, + { EMU_BTN_SCREENSHOT, "Save Screenshot" }, { EMU_BTN_NONE, nullptr } }; @@ -3088,7 +3146,7 @@ static void gui_display_content() { GameMedia game; GameBoxart art = boxart.getBoxartAndLoad(game); - ImguiTexture tex(art.boxartPath); + ImguiFileTexture tex(art.boxartPath); pressed = gameImageButton(tex, "Dreamcast BIOS", responsiveBoxVec2, "Dreamcast BIOS"); } else @@ -3130,7 +3188,7 @@ static void gui_display_content() // Put the image inside a child window so we can detect when it's fully clipped and doesn't need to be loaded if (ImGui::BeginChild("img", ImVec2(0, 0), ImGuiChildFlags_AutoResizeX | ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_NavFlattened)) { - ImguiTexture tex(art.boxartPath); + ImguiFileTexture tex(art.boxartPath); pressed = gameImageButton(tex, game.name, responsiveBoxVec2, gameName); } ImGui::EndChild(); @@ -3417,7 +3475,10 @@ void gui_display_ui() break; } error_popup(); + ImGui::Render(); gui_endFrame(gui_open); + uiThreadRunner.execTasks(); + ImguiFileTexture::resetLoadCount(); if (gui_state == GuiState::Closed) emu.start(); @@ -3447,7 +3508,7 @@ static std::string getFPSNotification() return std::string(settings.input.fastForwardMode ? ">>" : ""); } -void gui_display_osd() +void gui_draw_osd() { if (gui_state == GuiState::VJoyEdit) return; @@ -3488,7 +3549,13 @@ void gui_display_osd() } if (!settings.raHardcoreMode) lua::overlay(); + ImGui::Render(); + uiThreadRunner.execTasks(); +} +void gui_display_osd() +{ + gui_draw_osd(); gui_endFrame(gui_is_open()); } @@ -3523,7 +3590,7 @@ void gui_display_profiler() } ImGui::End(); - + ImGui::Render(); gui_endFrame(true); #endif } @@ -3604,17 +3671,22 @@ void gui_loadState() } } -void gui_saveState() +void gui_saveState(bool stopRestart) { const LockGuard lock(guiMutex); if (gui_state == GuiState::Closed && savestateAllowed()) { try { - emu.stop(); - dc_savestate(config::SavestateSlot); - emu.start(); + if (stopRestart) + emu.stop(); + savestate(); + if (stopRestart) + emu.start(); } catch (const FlycastException& e) { - gui_stop_game(e.what()); + if (stopRestart) + gui_stop_game(e.what()); + else + WARN_LOG(COMMON, "gui_saveState: %s", e.what()); } } } @@ -3641,6 +3713,33 @@ std::string gui_getCurGameBoxartUrl() return art.boxartUrl; } +void gui_takeScreenshot() +{ + if (!game_started) + return; + uiThreadRunner.runOnThread([]() { + std::string date = timeToString(time(nullptr)); + std::replace(date.begin(), date.end(), '/', '-'); + std::replace(date.begin(), date.end(), ':', '-'); + std::string name = "Flycast-" + date + ".png"; + + std::vector data; + getScreenshot(data); + if (data.empty()) { + gui_display_notification("No screenshot available", 2000); + } + else + { + try { + hostfs::saveScreenshot(name, data); + gui_display_notification("Screenshot saved", 2000, name.c_str()); + } catch (const FlycastException& e) { + gui_display_notification("Error saving screenshot", 5000, e.what()); + } + } + }); +} + #ifdef TARGET_UWP // Ugly but a good workaround for MS stupidity // UWP doesn't allow the UI thread to wait on a thread/task. When an std::future is ready, it is possible diff --git a/core/ui/gui.h b/core/ui/gui.h index 593ba32b8..ced181f0e 100644 --- a/core/ui/gui.h +++ b/core/ui/gui.h @@ -26,6 +26,7 @@ void gui_initFonts(); void gui_open_settings(); void gui_display_ui(); void gui_display_notification(const char *msg, int duration, const char *details = nullptr); +void gui_draw_osd(); void gui_display_osd(); void gui_display_profiler(); void gui_open_onboarding(); @@ -49,8 +50,9 @@ void gui_error(const std::string& what); void gui_setOnScreenKeyboardCallback(void (*callback)(bool show)); void gui_save(); void gui_loadState(); -void gui_saveState(); +void gui_saveState(bool stopRestart = true); std::string gui_getCurGameBoxartUrl(); +void gui_takeScreenshot(); enum class GuiState { Closed, diff --git a/core/ui/gui_achievements.cpp b/core/ui/gui_achievements.cpp index 7a8d84b29..c60cf0a82 100644 --- a/core/ui/gui_achievements.cpp +++ b/core/ui/gui_achievements.cpp @@ -77,7 +77,7 @@ void Notification::notify(Type type, const std::string& image, const std::string void Notification::showChallenge(const std::string& image) { std::lock_guard _(mutex); - ImguiTexture texture{ image }; + ImguiFileTexture texture{ image }; if (std::find(challenges.begin(), challenges.end(), texture) != challenges.end()) return; challenges.push_back(texture); @@ -183,7 +183,7 @@ bool Notification::draw() dl->AddRect(pos, pos + totalSize, borderCol, 0.f); pos += padding; - for (const auto& img : challenges) { + for (auto& img : challenges) { img.draw(dl, pos, size, alpha); pos.x += hspacing + size.x; } @@ -283,7 +283,7 @@ void achievementList() float w = ImGui::GetWindowContentRegionMax().x - ImGui::CalcTextSize("Close").x - ImGui::GetStyle().ItemSpacing.x * 2 - ImGui::GetStyle().WindowPadding.x - uiScaled(80.f + 20.f * 2); // image width and button frame padding Game game = getCurrentGame(); - ImguiTexture tex(game.image); + ImguiFileTexture tex(game.image); tex.draw(ScaledVec2(80.f, 80.f)); ImGui::SameLine(); ImGui::BeginChild("game_info", ImVec2(w, uiScaled(80.f)), ImGuiChildFlags_None, ImGuiWindowFlags_None); @@ -330,7 +330,7 @@ void achievementList() ImGui::Unindent(uiScaled(10)); } ImguiID _("achiev" + std::to_string(id++)); - ImguiTexture tex(ach.image); + ImguiFileTexture tex(ach.image); tex.draw(ScaledVec2(80.f, 80.f)); ImGui::SameLine(); ImGui::BeginChild(ImGui::GetID("ach_item"), ImVec2(0, 0), ImGuiChildFlags_AutoResizeY, ImGuiWindowFlags_None); diff --git a/core/ui/gui_achievements.h b/core/ui/gui_achievements.h index a0ea9658a..c41587799 100644 --- a/core/ui/gui_achievements.h +++ b/core/ui/gui_achievements.h @@ -51,10 +51,10 @@ private: u64 startTime = 0; u64 endTime = 0; Type type = Type::None; - ImguiTexture image; + ImguiFileTexture image; std::string text[3]; std::mutex mutex; - std::vector challenges; + std::vector challenges; std::map leaderboards; }; diff --git a/core/ui/gui_util.cpp b/core/ui/gui_util.cpp index 8621fb231..a037d0e78 100644 --- a/core/ui/gui_util.cpp +++ b/core/ui/gui_util.cpp @@ -29,6 +29,8 @@ #include "imgui.h" #include "imgui_internal.h" #include "stdclass.h" +#include "rend/osd.h" +#include static std::string select_current_directory = "**home**"; static std::vector subfolders; @@ -691,13 +693,6 @@ void windowDragScroll() } } -ImTextureID ImguiTexture::getId() const -{ - if (path.empty()) - return {}; - return imguiDriver->getOrLoadTexture(path); -} - static void setUV(float ar, ImVec2& uv0, ImVec2& uv1) { uv0 = { 0.f, 0.f }; @@ -715,49 +710,182 @@ static void setUV(float ar, ImVec2& uv0, ImVec2& uv1) } } -void ImguiTexture::draw(const ImVec2& size, const ImVec4& tint_col, const ImVec4& border_col) const +void ImguiTexture::draw(const ImVec2& size, const ImVec4& tint_col, const ImVec4& border_col) { ImTextureID id = getId(); if (id == ImTextureID{}) ImGui::Dummy(size); else { - float ar = imguiDriver->getAspectRatio(id); + const float ar = imguiDriver->getAspectRatio(id); + ImVec2 drawSize(size); + if (size.x == 0.f) + drawSize.x = size.y * ar; + else if (size.y == 0.f) + drawSize.y = size.x / ar; ImVec2 uv0, uv1; - setUV(ar, uv0, uv1); - ImGui::Image(id, size, uv0, uv1, tint_col, border_col); + setUV(ar / drawSize.x * drawSize.y, uv0, uv1); + ImGui::Image(id, drawSize, uv0, uv1, tint_col, border_col); } } -void ImguiTexture::draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& size, float alpha) const +void ImguiTexture::draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& size, float alpha) { ImTextureID id = getId(); if (id == ImTextureID{}) return; - float ar = imguiDriver->getAspectRatio(id); + const float ar = imguiDriver->getAspectRatio(id); ImVec2 uv0, uv1; - setUV(ar, uv0, uv1); - ImVec2 pos_b = pos + size; + setUV(ar / size.x * size.y, uv0, uv1); u32 col = alphaOverride(0xffffff, alpha); - drawList->AddImage(id, pos, pos_b, uv0, uv1, col); + drawList->AddImage(id, pos, pos + size, uv0, uv1, col); } bool ImguiTexture::button(const char* str_id, const ImVec2& image_size, const std::string& title, - const ImVec4& bg_col, const ImVec4& tint_col) const + const ImVec4& bg_col, const ImVec4& tint_col) { ImTextureID id = getId(); if (id == ImTextureID{}) return ImGui::Button(title.c_str(), image_size); else { - float ar = imguiDriver->getAspectRatio(id); + const float ar = imguiDriver->getAspectRatio(id); + const ImVec2 size = image_size - ImGui::GetStyle().FramePadding * 2; ImVec2 uv0, uv1; - setUV(ar, uv0, uv1); - ImVec2 size = image_size - ImGui::GetStyle().FramePadding * 2; + setUV(ar / size.x * size.y, uv0, uv1); return ImGui::ImageButton(str_id, id, size, uv0, uv1, bg_col, tint_col); } } +static u8 *loadImage(const std::string& path, int& width, int& height) +{ + FILE *file = nowide::fopen(path.c_str(), "rb"); + if (file == nullptr) + return nullptr; + + int channels; + stbi_set_flip_vertically_on_load(0); + u8 *imgData = stbi_load_from_file(file, &width, &height, &channels, STBI_rgb_alpha); + std::fclose(file); + return imgData; +} + +int ImguiFileTexture::textureLoadCount; + +ImTextureID ImguiFileTexture::getId() +{ + if (path.empty()) + return {}; + ImTextureID id = imguiDriver->getTexture(path); + if (id == ImTextureID() && textureLoadCount < 10) + { + textureLoadCount++; + int width, height; + u8 *imgData = loadImage(path, width, height); + if (imgData != nullptr) + { + try { + id = imguiDriver->updateTextureAndAspectRatio(path, imgData, width, height, nearestSampling); + } catch (...) { + // vulkan can throw during resizing + } + free(imgData); + } + } + return id; +} + +bool ImguiStateTexture::exists() +{ + std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); + try { + hostfs::storage().getFileInfo(path); + return true; + } catch (...) { + return false; + } +} + +ImTextureID ImguiStateTexture::getId() +{ + std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); + ImTextureID texid = imguiDriver->getTexture(path); + if (texid == ImTextureID()) + { + // load savestate info + std::vector pngData; + dc_getStateScreenshot(config::SavestateSlot, pngData); + if (pngData.empty()) + return {}; + + int width, height, channels; + stbi_set_flip_vertically_on_load(0); + u8 *imgData = stbi_load_from_memory(&pngData[0], pngData.size(), &width, &height, &channels, STBI_rgb_alpha); + if (imgData != nullptr) + { + try { + texid = imguiDriver->updateTextureAndAspectRatio(path, imgData, width, height, nearestSampling); + } catch (...) { + // vulkan can throw during resizing + } + free(imgData); + } + } + return texid; +} + +void ImguiStateTexture::invalidate() +{ + if (imguiDriver) + { + std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); + imguiDriver->deleteTexture(path); + } +} + +std::array ImguiVmuTexture::Vmus { 0, 1, 2, 3, 4, 5, 6, 7 }; +constexpr float VMU_WIDTH = 96.f; +constexpr float VMU_HEIGHT = 64.f; +constexpr float VMU_PADDING = 8.f; + +ImTextureID ImguiVmuTexture::getId() +{ + if (!vmu_lcd_status[index]) + return {}; + if (idPath.empty()) + idPath = ":vmu:" + std::to_string(index); + ImTextureID texid = imguiDriver->getTexture(idPath); + if (texid == ImTextureID() || vmuLastChanged != ::vmuLastChanged[index]) + { + try { + texid = imguiDriver->updateTexture(idPath, (const u8 *)vmu_lcd_data[index], 48, 32, true); + vmuLastChanged = ::vmuLastChanged[index]; + } catch (...) { + } + } + return texid; +} + +void ImguiVmuTexture::displayVmus(const ImVec2& pos) +{ + const ScaledVec2 size(VMU_WIDTH, VMU_HEIGHT); + const float padding = uiScaled(VMU_PADDING); + ImDrawList *dl = ImGui::GetForegroundDrawList(); + ImVec2 cpos(pos + ScaledVec2(2.f, 0)); // 96 pixels wide + 2 * 2 -> 100 + for (int i = 0; i < 8; i++) + { + if (!vmu_lcd_status[i]) + continue; + + ImTextureID texid = Vmus[i].getId(); + if (texid == ImTextureID()) + continue; + ImVec2 pos_b = cpos + size; + dl->AddImage(texid, cpos, pos_b, ImVec2(0, 1), ImVec2(1, 0), 0x80ffffff); + cpos.y += size.y + padding; + } +} + // Custom version of ImGui::BeginListBox that allows passing window flags bool BeginListBox(const char* label, const ImVec2& size_arg, ImGuiWindowFlags windowFlags) { @@ -827,8 +955,9 @@ bool Toast::draw() // Fade out alpha = (std::cos((now - endTime) / (float)END_ANIM_TIME * (float)M_PI) + 1.f) / 2.f; + const ImVec2 displaySize(ImGui::GetIO().DisplaySize); + const float maxW = std::min(uiScaled(640.f), displaySize.x); ImFont *regularFont = ImGui::GetFont(); - const float maxW = uiScaled(640.f); const ImVec2 titleSize = title.empty() ? ImVec2() : largeFont->CalcTextSizeA(largeFont->FontSize, FLT_MAX, maxW, &title.front(), &title.back() + 1); const ImVec2 msgSize = message.empty() ? ImVec2() @@ -838,7 +967,6 @@ bool Toast::draw() ImVec2 totalSize(std::max(titleSize.x, msgSize.x), titleSize.y + msgSize.y); totalSize += padding * 2.f + spacing * (float)(!title.empty() && !message.empty()); - const ImVec2 displaySize(ImGui::GetIO().DisplaySize); ImVec2 pos(insetLeft, displaySize.y - totalSize.y); if (now - startTime < START_ANIM_TIME) // Slide up diff --git a/core/ui/gui_util.h b/core/ui/gui_util.h index c52f1956c..957ce994e 100644 --- a/core/ui/gui_util.h +++ b/core/ui/gui_util.h @@ -199,27 +199,70 @@ public: class ImguiTexture { public: - ImguiTexture() = default; - ImguiTexture(const std::string& path) : path(path) {} - void draw(const ImVec2& size, const ImVec4& tint_col = ImVec4(1, 1, 1, 1), - const ImVec4& border_col = ImVec4(0, 0, 0, 0)) const; - void draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& size, float alpha = 1.f) const; + const ImVec4& border_col = ImVec4(0, 0, 0, 0)); + void draw(ImDrawList *drawList, const ImVec2& pos, const ImVec2& size, float alpha = 1.f); bool button(const char* str_id, const ImVec2& image_size, const std::string& title = {}, const ImVec4& bg_col = ImVec4(0, 0, 0, 0), - const ImVec4& tint_col = ImVec4(1, 1, 1, 1)) const; - - ImTextureID getId() const; + const ImVec4& tint_col = ImVec4(1, 1, 1, 1)); operator ImTextureID() { return getId(); } + void setNearestSampling(bool nearestSampling) { + this->nearestSampling = nearestSampling; + } - bool operator==(const ImguiTexture& other) const { + virtual ImTextureID getId() = 0; + virtual ~ImguiTexture() = default; + +protected: + bool nearestSampling = false; +}; + +class ImguiFileTexture : public ImguiTexture +{ +public: + ImguiFileTexture() = default; + ImguiFileTexture(const std::string& path) : ImguiTexture(), path(path) {} + + bool operator==(const ImguiFileTexture& other) const { return other.path == path; } + ImTextureID getId() override; + + static void resetLoadCount() { + textureLoadCount = 0; + } private: std::string path; + static int textureLoadCount; +}; + +class ImguiStateTexture : public ImguiTexture +{ +public: + ImTextureID getId() override; + + bool exists(); + void invalidate(); +}; + +class ImguiVmuTexture : public ImguiTexture +{ +public: + ImguiVmuTexture(int index = 0) : index(index) {} + + // draw all active vmus in a single column at the given position + static void displayVmus(const ImVec2& pos); + ImTextureID getId() override; + +private: + int index = 0; + std::string idPath; + u64 vmuLastChanged = 0; + + static std::array Vmus; }; static inline bool iconButton(const char *icon, const std::string& label, const ImVec2& size = {}) diff --git a/core/ui/imgui_driver.cpp b/core/ui/imgui_driver.cpp deleted file mode 100644 index e6b36b3f4..000000000 --- a/core/ui/imgui_driver.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - Copyright 2024 flyinghead - - This file is part of Flycast. - - Flycast is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 2 of the License, or - (at your option) any later version. - - Flycast is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Flycast. If not, see . -*/ -#include "imgui_driver.h" -#include "gui_util.h" -#include "rend/osd.h" -#define STBI_ONLY_JPEG -#define STBI_ONLY_PNG -#include - -constexpr float VMU_WIDTH = 96.f; -constexpr float VMU_HEIGHT = 64.f; -constexpr float VMU_PADDING = 8.f; - -void ImGuiDriver::reset() -{ - aspectRatios.clear(); - for (auto& tex : vmu_lcd_tex_ids) - tex = ImTextureID{}; - textureLoadCount = 0; - vmuLastChanged.fill({}); -} - -static u8 *loadImage(const std::string& path, int& width, int& height) -{ - FILE *file = nowide::fopen(path.c_str(), "rb"); - if (file == nullptr) - return nullptr; - - int channels; - stbi_set_flip_vertically_on_load(0); - u8 *imgData = stbi_load_from_file(file, &width, &height, &channels, STBI_rgb_alpha); - std::fclose(file); - return imgData; -} - -ImTextureID ImGuiDriver::getOrLoadTexture(const std::string& path, bool nearestSampling) -{ - ImTextureID id = getTexture(path); - if (id == ImTextureID() && textureLoadCount < 10) - { - textureLoadCount++; - int width, height; - u8 *imgData = loadImage(path, width, height); - if (imgData != nullptr) - { - try { - id = updateTextureAndAspectRatio(path, imgData, width, height, nearestSampling); - } catch (...) { - // vulkan can throw during resizing - } - free(imgData); - } - } - return id; -} - -void ImGuiDriver::updateVmuTextures() -{ - for (int i = 0; i < 8; i++) - { - if (!vmu_lcd_status[i]) - continue; - - if (this->vmuLastChanged[i] != ::vmuLastChanged[i] || vmu_lcd_tex_ids[i] == ImTextureID()) - { - try { - vmu_lcd_tex_ids[i] = updateTexture("__vmu" + std::to_string(i), (const u8 *)vmu_lcd_data[i], 48, 32, true); - } catch (...) { - continue; - } - if (vmu_lcd_tex_ids[i] != ImTextureID()) - this->vmuLastChanged[i] = ::vmuLastChanged[i]; - } - } -} - -void ImGuiDriver::displayVmus(const ImVec2& pos) -{ - updateVmuTextures(); - const ScaledVec2 size(VMU_WIDTH, VMU_HEIGHT); - const float padding = uiScaled(VMU_PADDING); - ImDrawList *dl = ImGui::GetForegroundDrawList(); - ImVec2 cpos(pos + ScaledVec2(2.f, 0)); // 96 pixels wide + 2 * 2 -> 100 - for (int i = 0; i < 8; i++) - { - if (!vmu_lcd_status[i]) - continue; - - ImVec2 pos_b = cpos + size; - dl->AddImage(vmu_lcd_tex_ids[i], cpos, pos_b, ImVec2(0, 1), ImVec2(1, 0), 0x80ffffff); - cpos.y += size.y + padding; - } -} - diff --git a/core/ui/imgui_driver.h b/core/ui/imgui_driver.h index 3a77a7f6e..68d20e599 100644 --- a/core/ui/imgui_driver.h +++ b/core/ui/imgui_driver.h @@ -30,54 +30,47 @@ public: gui_initFonts(); } virtual ~ImGuiDriver() = default; - virtual void reset(); + + virtual void reset() { + aspectRatios.clear(); + } virtual void newFrame() = 0; virtual void renderDrawData(ImDrawData* drawData, bool gui_open) = 0; virtual void displayVmus() {} // TODO OpenGL only. Get rid of it virtual void displayCrosshairs() {} // same - // draw all active vmus in a single column at the given position - void displayVmus(const ImVec2& pos); - - void doPresent() { - textureLoadCount = 0; - present(); - } + virtual void present() = 0; virtual void setFrameRendered() {} - float getAspectRatio(ImTextureID textureId) { - auto it = aspectRatios.find(textureId); - if (it != aspectRatios.end()) - return it->second; - else - return 1; - } - - ImTextureID getOrLoadTexture(const std::string& path, bool nearestSampling = false); - -protected: virtual ImTextureID getTexture(const std::string& name) = 0; virtual ImTextureID updateTexture(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) = 0; - virtual void present() = 0; - void updateVmuTextures(); + virtual void deleteTexture(const std::string& name) = 0; - ImTextureID vmu_lcd_tex_ids[8] {}; - std::array vmuLastChanged {}; - -private: ImTextureID updateTextureAndAspectRatio(const std::string& name, const u8 *data, int width, int height, bool nearestSampling) { - textureLoadCount++; ImTextureID textureId = updateTexture(name, data, width, height, nearestSampling); if (textureId != ImTextureID()) aspectRatios[textureId] = (float)width / height; return textureId; } - std::unordered_map aspectRatios; - int textureLoadCount = 0; + float getAspectRatio(ImTextureID textureId) + { + auto it = aspectRatios.find(textureId); + if (it != aspectRatios.end()) + return it->second; + else + return 1.f; + } + void updateAspectRatio(ImTextureID textureId, float aspectRatio) { + if (textureId != ImTextureID()) + aspectRatios[textureId] = aspectRatio; + } + +private: + std::unordered_map aspectRatios; // TODO move this out }; extern std::unique_ptr imguiDriver; diff --git a/core/ui/mainui.cpp b/core/ui/mainui.cpp index 7a9a84192..8191679e4 100644 --- a/core/ui/mainui.cpp +++ b/core/ui/mainui.cpp @@ -96,7 +96,7 @@ void mainui_loop() if (imguiDriver == nullptr) forceReinit = true; else - imguiDriver->doPresent(); + imguiDriver->present(); if (config::RendererType != currentRenderer || forceReinit) { diff --git a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/AndroidStorage.java b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/AndroidStorage.java index ce56bdaeb..0858f9928 100644 --- a/shell/android-studio/flycast/src/main/java/com/flycast/emulator/AndroidStorage.java +++ b/shell/android-studio/flycast/src/main/java/com/flycast/emulator/AndroidStorage.java @@ -24,17 +24,22 @@ import android.content.ContentUris; import android.content.CursorLoader; import android.content.Intent; import android.database.Cursor; +import android.media.MediaScannerConnection; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; import android.provider.MediaStore; +import android.util.Log; import androidx.documentfile.provider.DocumentFile; import java.io.File; import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.List; public class AndroidStorage { @@ -386,4 +391,35 @@ public class AndroidStorage { cursor.close(); return result; } + + public void saveScreenshot(String name, byte data[]) + { + File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + File file = new File(path, name); + + try { + // Make sure the Pictures directory exists. + path.mkdirs(); + + OutputStream os = new FileOutputStream(file); + try { + os.write(data); + } catch (IOException e) { + try { os.close(); } catch (IOException e1) {} + file.delete(); + throw e; + } + os.close(); + + // Tell the media scanner about the new file so that it is + // immediately available to the user. + MediaScannerConnection.scanFile(activity, + new String[] { file.toString() }, null, null); + } catch (IOException e) { + // Unable to create file, likely because external storage is + // not currently mounted. + Log.w("flycast", "saveScreenshot: Error writing " + file, e); + throw new RuntimeException(e.getMessage()); + } + } } diff --git a/shell/android-studio/flycast/src/main/jni/src/android_storage.h b/shell/android-studio/flycast/src/main/jni/src/android_storage.h index 165842996..af7ee59b8 100644 --- a/shell/android-studio/flycast/src/main/jni/src/android_storage.h +++ b/shell/android-studio/flycast/src/main/jni/src/android_storage.h @@ -38,6 +38,7 @@ public: jgetSubPath = env->GetMethodID(clazz, "getSubPath", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); jgetFileInfo = env->GetMethodID(clazz, "getFileInfo", "(Ljava/lang/String;)Lcom/flycast/emulator/FileInfo;"); jaddStorage = env->GetMethodID(clazz, "addStorage", "(ZZ)V"); + jsaveScreenshot = env->GetMethodID(clazz, "saveScreenshot", "(Ljava/lang/String;[B)V"); } bool isKnownPath(const std::string& path) override { @@ -137,6 +138,15 @@ public: } } + void saveScreenshot(const std::string& name, const std::vector& data) + { + jni::String jname(name); + jni::ByteArray jdata(data.size()); + jdata.setData(&data[0]); + jni::env()->CallVoidMethod(jstorage, jsaveScreenshot, (jstring)jname, (jbyteArray)jdata); + checkException(); + } + private: void checkException() { @@ -184,6 +194,7 @@ private: jmethodID jaddStorage; jmethodID jgetSubPath; jmethodID jgetFileInfo; + jmethodID jsaveScreenshot; // FileInfo accessors lazily initialized to avoid having to load the class jmethodID jgetName = nullptr; jmethodID jgetPath = nullptr; @@ -201,6 +212,11 @@ Storage& customStorage() return *androidStorage; } +void saveScreenshot(const std::string& name, const std::vector& data) +{ + return static_cast(customStorage()).saveScreenshot(name, data); +} + } // namespace hostfs extern "C" JNIEXPORT void JNICALL Java_com_flycast_emulator_AndroidStorage_addStorageCallback(JNIEnv *env, jobject obj, jstring path) diff --git a/shell/apple/emulator-ios/emulator/ios_main.mm b/shell/apple/emulator-ios/emulator/ios_main.mm index 0fe36432c..20cc34677 100644 --- a/shell/apple/emulator-ios/emulator/ios_main.mm +++ b/shell/apple/emulator-ios/emulator/ios_main.mm @@ -19,6 +19,7 @@ */ #import +#include #include int darw_printf(const char* text,...) @@ -45,3 +46,15 @@ std::string os_Locale(){ std::string os_PrecomposedString(std::string string){ return [[[NSString stringWithUTF8String:string.c_str()] precomposedStringWithCanonicalMapping] UTF8String]; } + +namespace hostfs +{ + +void saveScreenshot(const std::string& name, const std::vector& data) +{ + NSData* imageData = [NSData dataWithBytes:&data[0] length:data.size()]; + UIImage* pngImage = [UIImage imageWithData:imageData]; + UIImageWriteToSavedPhotosAlbum(pngImage, nil, nil, nil); +} + +} diff --git a/shell/apple/emulator-ios/plist.in b/shell/apple/emulator-ios/plist.in index 5d7330ccc..e86c2f7cd 100644 --- a/shell/apple/emulator-ios/plist.in +++ b/shell/apple/emulator-ios/plist.in @@ -70,6 +70,8 @@ NSMicrophoneUsageDescription Flycast requires microphone access to emulate the Dreamcast microphone + NSPhotoLibraryAddUsageDescription + Flycast can save screenshots to your Photo library. UISupportsDocumentBrowser LSSupportsOpeningDocumentsInPlace diff --git a/shell/apple/emulator-osx/emulator-osx/osx-main.mm b/shell/apple/emulator-osx/emulator-osx/osx-main.mm index 089ce81f4..fe58521c1 100644 --- a/shell/apple/emulator-osx/emulator-osx/osx-main.mm +++ b/shell/apple/emulator-osx/emulator-osx/osx-main.mm @@ -255,3 +255,14 @@ void os_VideoRoutingTermVk() [syphonMtlServer release]; syphonMtlServer = NULL; } + +namespace hostfs +{ + +std::string getScreenshotsPath() +{ + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES); + return [[paths objectAtIndex:0] UTF8String]; +} + +} diff --git a/shell/libretro/oslib.cpp b/shell/libretro/oslib.cpp index cbb46b3bc..cf70ad53e 100644 --- a/shell/libretro/oslib.cpp +++ b/shell/libretro/oslib.cpp @@ -130,16 +130,6 @@ std::string getTextureDumpPath() } -void dc_savestate(int index = 0) -{ - die("unsupported"); -} - -void dc_loadstate(int index = 0) -{ - die("unsupported"); -} - #ifdef _WIN32 void os_SetThreadName(const char *name) { } From de403a6203dcacad72ad237b78a5f9685d7a0789 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 13 May 2024 16:12:32 +0200 Subject: [PATCH 70/86] switch, uwp, libretro build fixes --- core/oslib/oslib.cpp | 4 ++-- core/rend/dx11/dx11_renderer.cpp | 6 +++++- shell/switch/switch_main.cpp | 11 +++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/core/oslib/oslib.cpp b/core/oslib/oslib.cpp index 834ade0ef..976620a30 100644 --- a/core/oslib/oslib.cpp +++ b/core/oslib/oslib.cpp @@ -199,7 +199,7 @@ void saveScreenshot(const std::string& name, const std::vector& data) StorageFolder^ folder = KnownFolders::PicturesLibrary; // or SavedPictures? if (folder == nullptr) { INFO_LOG(COMMON, "KnownFolders::PicturesLibrary is null"); - throw FlycastException(); + throw FlycastException("Can't find Pictures library"); } nowide::wstackstring wstr; wchar_t *wname = wstr.convert(name.c_str()); @@ -220,7 +220,7 @@ void saveScreenshot(const std::string& name, const std::vector& data) } catch (COMException^ e) { WARN_LOG(COMMON, "Save screenshot failed: %S", e->Message->Data()); - throw FlycastException(); + throw FlycastException(""); } } diff --git a/core/rend/dx11/dx11_renderer.cpp b/core/rend/dx11/dx11_renderer.cpp index 2cb9e9357..cd5cb76fa 100644 --- a/core/rend/dx11/dx11_renderer.cpp +++ b/core/rend/dx11/dx11_renderer.cpp @@ -1384,8 +1384,12 @@ bool DX11Renderer::GetLastFrame(std::vector& data, int& width, int& height) quad->draw(fbTextureView, samplers->getSampler(true), nullptr, -1.f, -1.f, 2.f, 2.f, config::Rotate90); +#ifndef LIBRETRO deviceContext->OMSetRenderTargets(1, &theDX11Context.getRenderTarget().get(), nullptr); - +#else + ID3D11RenderTargetView *nullView = nullptr; + deviceContext->OMSetRenderTargets(1, &nullView, nullptr); +#endif D3D11_TEXTURE2D_DESC desc; dstTex->GetDesc(&desc); desc.Usage = D3D11_USAGE_STAGING; diff --git a/shell/switch/switch_main.cpp b/shell/switch/switch_main.cpp index ffc102655..2d710796a 100644 --- a/shell/switch/switch_main.cpp +++ b/shell/switch/switch_main.cpp @@ -21,6 +21,8 @@ #include "emulator.h" #include "ui/mainui.h" #include "oslib/directory.h" +#include +#include int main(int argc, char *argv[]) { @@ -58,4 +60,13 @@ void os_DoEvents() { } +namespace hostfs +{ + +void saveScreenshot(const std::string& name, const std::vector& data) +{ + throw FlycastException("Not supported on Switch"); +} + +} #endif //!LIBRETRO From b5f49d6c3a293f824c5e8863122c312ac802526d Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Mon, 13 May 2024 16:27:31 +0200 Subject: [PATCH 71/86] ios build fix --- shell/apple/emulator-ios/emulator/ios_main.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/apple/emulator-ios/emulator/ios_main.mm b/shell/apple/emulator-ios/emulator/ios_main.mm index 20cc34677..0c1fc8c02 100644 --- a/shell/apple/emulator-ios/emulator/ios_main.mm +++ b/shell/apple/emulator-ios/emulator/ios_main.mm @@ -18,7 +18,7 @@ along with Flycast. If not, see . */ #import - +#include "types.h" #include #include From 35acb7e62c4a6de10061799932a61b70d7aa7d52 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 14 May 2024 14:23:40 +0200 Subject: [PATCH 72/86] ui: load savestate thumbnails asynchronously. limit thumbnail size GetLastFrame can take max width or height. Limit width of savestate screenshot to 640. Load savestate thumbnail in async task. --- core/hw/pvr/Renderer_if.h | 2 ++ core/rend/dx11/dx11_renderer.cpp | 25 +++++++++++------ core/rend/dx9/d3d_renderer.cpp | 25 +++++++++++------ core/rend/gles/gldraw.cpp | 25 +++++++++++------ core/rend/vulkan/vulkan_context.cpp | 25 +++++++++++------ core/ui/gui.cpp | 7 +++-- core/ui/gui_util.cpp | 43 +++++++++++++++++++---------- core/ui/gui_util.h | 9 ++++++ 8 files changed, 111 insertions(+), 50 deletions(-) diff --git a/core/hw/pvr/Renderer_if.h b/core/hw/pvr/Renderer_if.h index 191368630..d7f7f17a8 100644 --- a/core/hw/pvr/Renderer_if.h +++ b/core/hw/pvr/Renderer_if.h @@ -65,6 +65,8 @@ struct Renderer virtual bool RenderLastFrame() { return false; } // Get the last rendered frame pixel data in RGB format // The returned image is rotated and scaled (upward orientation and square pixels) + // If both width and height are zero, the internal render resolution will be used. + // Otherwise either width or height will be used as the maximum width or height respectively. virtual bool GetLastFrame(std::vector& data, int& width, int& height) { return false; } virtual bool Present() { return true; } diff --git a/core/rend/dx11/dx11_renderer.cpp b/core/rend/dx11/dx11_renderer.cpp index cd5cb76fa..623529782 100644 --- a/core/rend/dx11/dx11_renderer.cpp +++ b/core/rend/dx11/dx11_renderer.cpp @@ -1350,16 +1350,25 @@ bool DX11Renderer::GetLastFrame(std::vector& data, int& width, int& height) if (!frameRenderedOnce) return false; - width = this->width; - height = this->height; - if (config::Rotate90) - std::swap(width, height); - // We need square pixels for PNG - int w = aspectRatio * height; - if (width > w) + if (width != 0) { height = width / aspectRatio; + } + else if (height != 0) { + width = aspectRatio * height; + } else - width = w; + { + width = this->width; + height = this->height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = aspectRatio * height; + if (width > w) + height = width / aspectRatio; + else + width = w; + } ComPtr dstTex; ComPtr dstRenderTarget; diff --git a/core/rend/dx9/d3d_renderer.cpp b/core/rend/dx9/d3d_renderer.cpp index ac58ab35a..63085ff42 100644 --- a/core/rend/dx9/d3d_renderer.cpp +++ b/core/rend/dx9/d3d_renderer.cpp @@ -1427,16 +1427,25 @@ bool D3DRenderer::GetLastFrame(std::vector& data, int& width, int& height) if (!frameRenderedOnce || !theDXContext.isReady()) return false; - width = this->width; - height = this->height; - if (config::Rotate90) - std::swap(width, height); - // We need square pixels for PNG - int w = aspectRatio * height; - if (width > w) + if (width != 0) { height = width / aspectRatio; + } + else if (height != 0) { + width = aspectRatio * height; + } else - width = w; + { + width = this->width; + height = this->height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = aspectRatio * height; + if (width > w) + height = width / aspectRatio; + else + width = w; + } backbuffer.reset(); device->GetRenderTarget(0, &backbuffer.get()); diff --git a/core/rend/gles/gldraw.cpp b/core/rend/gles/gldraw.cpp index 9108bfeae..85fbb874b 100644 --- a/core/rend/gles/gldraw.cpp +++ b/core/rend/gles/gldraw.cpp @@ -822,16 +822,25 @@ bool OpenGLRenderer::GetLastFrame(std::vector& data, int& width, int& height GlFramebuffer *framebuffer = gl.ofbo2.ready ? gl.ofbo2.framebuffer.get() : gl.ofbo.framebuffer.get(); if (framebuffer == nullptr) return false; - width = framebuffer->getWidth(); - height = framebuffer->getHeight(); - if (config::Rotate90) - std::swap(width, height); - // We need square pixels for PNG - int w = gl.ofbo.aspectRatio * height; - if (width > w) + if (width != 0) { height = width / gl.ofbo.aspectRatio; + } + else if (height != 0) { + width = gl.ofbo.aspectRatio * height; + } else - width = w; + { + width = framebuffer->getWidth(); + height = framebuffer->getHeight(); + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = gl.ofbo.aspectRatio * height; + if (width > w) + height = width / gl.ofbo.aspectRatio; + else + width = w; + } GlFramebuffer dstFramebuffer(width, height, false, false); diff --git a/core/rend/vulkan/vulkan_context.cpp b/core/rend/vulkan/vulkan_context.cpp index 6aede4e8e..f10e0fde2 100644 --- a/core/rend/vulkan/vulkan_context.cpp +++ b/core/rend/vulkan/vulkan_context.cpp @@ -1238,16 +1238,25 @@ bool VulkanContext::GetLastFrame(std::vector& data, int& width, int& height) if (!lastFrameView) return false; - width = lastFrameExtent.width; - height = lastFrameExtent.height; - if (config::Rotate90) - std::swap(width, height); - // We need square pixels for PNG - int w = lastFrameAR * height; - if (width > w) + if (width != 0) { height = width / lastFrameAR; + } + else if (height != 0) { + width = lastFrameAR * height; + } else - width = w; + { + width = lastFrameExtent.width; + height = lastFrameExtent.height; + if (config::Rotate90) + std::swap(width, height); + // We need square pixels for PNG + int w = lastFrameAR * height; + if (width > w) + height = width / lastFrameAR; + else + width = w; + } // color attachment FramebufferAttachment attachment(physicalDevice, *device); attachment.Init(width, height, vk::Format::eR8G8B8A8Unorm, vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eTransferSrc, "screenshot"); diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index b448dfa1f..49d717644 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -591,11 +591,11 @@ static void appendVectorData(void *context, void *data, int size) v.insert(v.end(), bytes, bytes + size); } -static void getScreenshot(std::vector& data) +static void getScreenshot(std::vector& data, int width = 0) { data.clear(); std::vector rawData; - int width, height; + int height = 0; if (renderer == nullptr || !renderer->GetLastFrame(rawData, width, height)) return; stbi_flip_vertically_on_write(0); @@ -621,8 +621,9 @@ static std::string timeToString(time_t time) static void savestate() { + // TODO save state async: png compression, savestate file compression/write std::vector pngData; - getScreenshot(pngData); + getScreenshot(pngData, 640); dc_savestate(config::SavestateSlot, pngData.empty() ? nullptr : &pngData[0], pngData.size()); ImguiStateTexture savestatePic; savestatePic.invalidate(); diff --git a/core/ui/gui_util.cpp b/core/ui/gui_util.cpp index a037d0e78..d9ee7f84b 100644 --- a/core/ui/gui_util.cpp +++ b/core/ui/gui_util.cpp @@ -795,6 +795,8 @@ ImTextureID ImguiFileTexture::getId() return id; } +std::future ImguiStateTexture::asyncLoad; + bool ImguiStateTexture::exists() { std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); @@ -810,28 +812,39 @@ ImTextureID ImguiStateTexture::getId() { std::string path = hostfs::getSavestatePath(config::SavestateSlot, false); ImTextureID texid = imguiDriver->getTexture(path); - if (texid == ImTextureID()) + if (texid != ImTextureID()) + return texid; + if (asyncLoad.valid()) { + if (asyncLoad.wait_for(std::chrono::seconds::zero()) == std::future_status::timeout) + return {}; + LoadedPic loadedPic = asyncLoad.get(); + if (loadedPic.data != nullptr) + { + try { + texid = imguiDriver->updateTextureAndAspectRatio(path, loadedPic.data, loadedPic.width, loadedPic.height, nearestSampling); + } catch (...) { + // vulkan can throw during resizing + } + free(loadedPic.data); + } + return texid; + } + asyncLoad = std::async(std::launch::async, []() { + LoadedPic loadedPic{}; // load savestate info std::vector pngData; dc_getStateScreenshot(config::SavestateSlot, pngData); if (pngData.empty()) - return {}; + return loadedPic; - int width, height, channels; + int channels; stbi_set_flip_vertically_on_load(0); - u8 *imgData = stbi_load_from_memory(&pngData[0], pngData.size(), &width, &height, &channels, STBI_rgb_alpha); - if (imgData != nullptr) - { - try { - texid = imguiDriver->updateTextureAndAspectRatio(path, imgData, width, height, nearestSampling); - } catch (...) { - // vulkan can throw during resizing - } - free(imgData); - } - } - return texid; + loadedPic.data = stbi_load_from_memory(&pngData[0], pngData.size(), &loadedPic.width, &loadedPic.height, &channels, STBI_rgb_alpha); + + return loadedPic; + }); + return {}; } void ImguiStateTexture::invalidate() diff --git a/core/ui/gui_util.h b/core/ui/gui_util.h index 957ce994e..8bb28f6ed 100644 --- a/core/ui/gui_util.h +++ b/core/ui/gui_util.h @@ -246,6 +246,15 @@ public: bool exists(); void invalidate(); + +private: + struct LoadedPic + { + u8 *data; + int width; + int height; + }; + static std::future asyncLoad; }; class ImguiVmuTexture : public ImguiTexture From 21c77adc4d56df28c71c3f0aff986f2d1e8035b2 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Tue, 14 May 2024 22:43:55 +0200 Subject: [PATCH 73/86] os_notify --- core/emulator.cpp | 8 +++++--- core/hw/maple/maple_devs.cpp | 3 +-- core/hw/naomi/card_reader.cpp | 5 ++--- core/hw/naomi/naomi.cpp | 4 ++-- core/hw/naomi/naomi_m3comm.cpp | 4 ++-- core/hw/naomi/printer.cpp | 7 ++++--- core/lua/lua.cpp | 2 +- core/network/ggpo.cpp | 12 ++++++------ core/network/naomi_network.cpp | 12 ++++++------ core/nullDC.cpp | 14 +++++++------- core/oslib/oslib.h | 1 + core/ui/gui.cpp | 14 +++++++------- core/ui/gui.h | 1 - shell/libretro/libretro.cpp | 12 ++++++------ 14 files changed, 50 insertions(+), 49 deletions(-) diff --git a/core/emulator.cpp b/core/emulator.cpp index d41b93eaa..b33e87c6e 100644 --- a/core/emulator.cpp +++ b/core/emulator.cpp @@ -37,7 +37,6 @@ #include "network/ggpo.h" #include "hw/mem/mem_watch.h" #include "network/net_handshake.h" -#include "ui/gui.h" #include "network/naomi_network.h" #include "serialize.h" #include "hw/pvr/pvr.h" @@ -45,6 +44,9 @@ #include "oslib/storage.h" #include "wsi/context.h" #include +#ifndef LIBRETRO +#include "ui/gui.h" +#endif settings_t settings; @@ -489,7 +491,7 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) nvmem::loadHle(); NOTICE_LOG(BOOT, "Did not load BIOS, using reios"); if (!config::UseReios && config::UseReios.isReadOnly()) - gui_display_notification("This game requires a real BIOS", 15000); + os_notify("This game requires a real BIOS", 15000); } } else @@ -541,7 +543,7 @@ void Emulator::loadGame(const char *path, LoadProgress *progress) cheatManager.reset(settings.content.gameId); if (cheatManager.isWidescreen()) { - gui_display_notification("Widescreen cheat activated", 2000); + os_notify("Widescreen cheat activated", 2000); config::ScreenStretching.override(134); // 4:3 -> 16:9 } // reload settings so that all settings can be overridden diff --git a/core/hw/maple/maple_devs.cpp b/core/hw/maple/maple_devs.cpp index a0bba1b55..5d89c25b7 100755 --- a/core/hw/maple/maple_devs.cpp +++ b/core/hw/maple/maple_devs.cpp @@ -7,7 +7,6 @@ #include "oslib/oslib.h" #include "hw/aica/sgc_if.h" #include "cfg/option.h" -#include "ui/gui.h" #include #include #include @@ -1746,7 +1745,7 @@ struct RFIDReaderWriter : maple_base cardLocked = false; cardInserted = false; NOTICE_LOG(MAPLE, "RFID card %d unlocked", player_num); - gui_display_notification("Card ejected", 2000); + os_notify("Card ejected", 2000); return (MapleDeviceRV)0xfe; case 0xB1: // write to card diff --git a/core/hw/naomi/card_reader.cpp b/core/hw/naomi/card_reader.cpp index 0877dee27..8dffa1cf1 100644 --- a/core/hw/naomi/card_reader.cpp +++ b/core/hw/naomi/card_reader.cpp @@ -21,7 +21,6 @@ #include "hw/sh4/modules/modules.h" #include "hw/maple/maple_cfg.h" #include "hw/maple/maple_devs.h" -#include "ui/gui.h" #include #include #include @@ -288,7 +287,7 @@ protected: case CARD_EJECT: NOTICE_LOG(NAOMI, "Card ejected"); if (cardInserted) - gui_display_notification("Card ejected", 2000); + os_notify("Card ejected", 2000); cardInserted = false; status1 = getStatus1(); break; @@ -582,7 +581,7 @@ private: case CARD_EJECT: NOTICE_LOG(NAOMI, "Card ejected"); if (cardInserted) - gui_display_notification("Card ejected", 2000); + os_notify("Card ejected", 2000); cardInserted = false; break; case CARD_NEW: diff --git a/core/hw/naomi/naomi.cpp b/core/hw/naomi/naomi.cpp index 6c4bbafd4..8eb2b0c98 100644 --- a/core/hw/naomi/naomi.cpp +++ b/core/hw/naomi/naomi.cpp @@ -30,7 +30,7 @@ #include "serialize.h" #include "network/output.h" #include "hw/sh4/modules/modules.h" -#include "ui/gui.h" +#include "oslib/oslib.h" #include "printer.h" #include "hw/flashrom/x76f100.h" @@ -512,7 +512,7 @@ struct DriveSimPipe : public SerialPort::Pipe { char message[16]; sprintf(message, "Speed: %3d", speed); - gui_display_notification(message, 1000); + os_notify(message, 1000); } } buffer.clear(); diff --git a/core/hw/naomi/naomi_m3comm.cpp b/core/hw/naomi/naomi_m3comm.cpp index b6027238a..29a928fd3 100644 --- a/core/hw/naomi/naomi_m3comm.cpp +++ b/core/hw/naomi/naomi_m3comm.cpp @@ -30,7 +30,7 @@ #include "hw/sh4/sh4_mem.h" #include "network/naomi_network.h" #include "emulator.h" -#include "ui/gui.h" +#include "oslib/oslib.h" #include #include @@ -72,7 +72,7 @@ void NaomiM3Comm::closeNetwork() void NaomiM3Comm::connectNetwork() { - gui_display_notification("Network started", 5000); + os_notify("Network started", 5000); packet_number = 0; slot_count = naomiNetwork.getSlotCount(); slot_id = naomiNetwork.getSlotId(); diff --git a/core/hw/naomi/printer.cpp b/core/hw/naomi/printer.cpp index ffb8e492b..3c5edc0e1 100644 --- a/core/hw/naomi/printer.cpp +++ b/core/hw/naomi/printer.cpp @@ -20,7 +20,7 @@ #include "stdclass.h" #include "printer.h" #include "serialize.h" -#include "ui/gui.h" +#include "oslib/oslib.h" #include #include #include @@ -827,10 +827,11 @@ private: state = Default; if (bitmapWriter && bitmapWriter->isDirty()) { + // TODO save to ~/Pictures instead std::string s = get_writable_data_path(settings.content.gameId + "-results.png"); bitmapWriter->save(s); bitmapWriter.reset(); - gui_display_notification("Print out saved", 5000, s.c_str()); + os_notify("Print out saved", 5000, s.c_str()); NOTICE_LOG(NAOMI, "%s", s.c_str()); } break; @@ -1197,7 +1198,7 @@ std::string get_writable_data_path(const std::string& s) return "./" + s; } -void gui_display_notification(char const*, int, char const*) { +void os_notify(char const*, int, char const*) { } int main(int argc, char *argv[]) diff --git a/core/lua/lua.cpp b/core/lua/lua.cpp index 96e928c3c..194eb1542 100644 --- a/core/lua/lua.cpp +++ b/core/lua/lua.cpp @@ -447,7 +447,7 @@ static void luaRegister(lua_State *L) gui_open_settings(); })) .addFunction("exit", dc_exit) - .addFunction("displayNotification", gui_display_notification) + .addFunction("displayNotification", os_notify) .endNamespace() .beginNamespace("config") diff --git a/core/network/ggpo.cpp b/core/network/ggpo.cpp index 00363a5e8..0672af39b 100644 --- a/core/network/ggpo.cpp +++ b/core/network/ggpo.cpp @@ -216,19 +216,19 @@ static bool on_event(GGPOEvent *info) switch (info->code) { case GGPO_EVENTCODE_CONNECTED_TO_PEER: INFO_LOG(NETWORK, "Connected to peer %d", info->u.connected.player); - gui_display_notification("Connected to peer", 2000); + os_notify("Connected to peer", 2000); break; case GGPO_EVENTCODE_SYNCHRONIZING_WITH_PEER: INFO_LOG(NETWORK, "Synchronizing with peer %d", info->u.synchronizing.player); - gui_display_notification("Synchronizing with peer", 2000); + os_notify("Synchronizing with peer", 2000); break; case GGPO_EVENTCODE_SYNCHRONIZED_WITH_PEER: INFO_LOG(NETWORK, "Synchronized with peer %d", info->u.synchronized.player); - gui_display_notification("Synchronized with peer", 2000); + os_notify("Synchronized with peer", 2000); break; case GGPO_EVENTCODE_RUNNING: INFO_LOG(NETWORK, "Running"); - gui_display_notification("Running", 2000); + os_notify("Running", 2000); synchronized = true; break; case GGPO_EVENTCODE_DISCONNECTED_FROM_PEER: @@ -242,11 +242,11 @@ static bool on_event(GGPOEvent *info) break; case GGPO_EVENTCODE_CONNECTION_INTERRUPTED: INFO_LOG(NETWORK, "Connection interrupted with player %d", info->u.connection_interrupted.player); - gui_display_notification("Connection interrupted", 2000); + os_notify("Connection interrupted", 2000); break; case GGPO_EVENTCODE_CONNECTION_RESUMED: INFO_LOG(NETWORK, "Connection resumed with player %d", info->u.connection_resumed.player); - gui_display_notification("Connection resumed", 2000); + os_notify("Connection resumed", 2000); break; } return true; diff --git a/core/network/naomi_network.cpp b/core/network/naomi_network.cpp index 16170cd53..43500056d 100644 --- a/core/network/naomi_network.cpp +++ b/core/network/naomi_network.cpp @@ -19,7 +19,7 @@ #include "naomi_network.h" #include "hw/naomi/naomi_flashrom.h" #include "cfg/option.h" -#include "ui/gui.h" +#include "oslib/oslib.h" #include #include @@ -104,7 +104,7 @@ bool NaomiNetwork::startNetwork() std::string notif = slaves.empty() ? "Waiting for players..." : std::to_string(slaves.size()) + " player(s) connected. Waiting..."; - gui_display_notification(notif.c_str(), timeout.count() * 2000); + os_notify(notif.c_str(), timeout.count() * 2000); poll(); @@ -125,12 +125,12 @@ bool NaomiNetwork::startNetwork() nextPeer = slaves[0].addr; - gui_display_notification("Starting game", 2000); + os_notify("Starting game", 2000); SetNaomiNetworkConfig(0); return true; } - gui_display_notification("No player connected", 8000); + os_notify("No player connected", 8000); } else { @@ -164,7 +164,7 @@ bool NaomiNetwork::startNetwork() } NOTICE_LOG(NETWORK, "Connecting to server"); - gui_display_notification("Connecting to server", 10000); + os_notify("Connecting to server", 10000); steady_clock::time_point start_time = steady_clock::now(); while (!networkStopping && !_startNow && steady_clock::now() - start_time < timeout) @@ -249,7 +249,7 @@ bool NaomiNetwork::receive(const sockaddr_in *addr, const Packet *packet, u32 si nextPeer.sin_port = packet->sync.nextNodePort; nextPeer.sin_addr.s_addr = packet->sync.nextNodeIp == 0 ? addr->sin_addr.s_addr : packet->sync.nextNodeIp; std::string notif = "Connected as slot " + std::to_string(slotId); - gui_display_notification(notif.c_str(), 2000); + os_notify(notif.c_str(), 2000); } break; diff --git a/core/nullDC.cpp b/core/nullDC.cpp index 5741127a2..5129cd830 100644 --- a/core/nullDC.cpp +++ b/core/nullDC.cpp @@ -128,7 +128,7 @@ void dc_savestate(int index, const u8 *pngData, u32 pngSize) if (data == nullptr) { WARN_LOG(SAVESTATE, "Failed to save state - could not malloc %d bytes", (int)ser.size()); - gui_display_notification("Save state failed - memory full", 5000); + os_notify("Save state failed - memory full", 5000); return; } @@ -140,7 +140,7 @@ void dc_savestate(int index, const u8 *pngData, u32 pngSize) if (f == nullptr) { WARN_LOG(SAVESTATE, "Failed to save state - could not open %s for writing", filename.c_str()); - gui_display_notification("Cannot open save file", 5000); + os_notify("Cannot open save file", 5000); free(data); return; } @@ -168,12 +168,12 @@ void dc_savestate(int index, const u8 *pngData, u32 pngSize) free(data); NOTICE_LOG(SAVESTATE, "Saved state to %s size %d", filename.c_str(), (int)ser.size()); - gui_display_notification("State saved", 2000); + os_notify("State saved", 2000); return; fail: WARN_LOG(SAVESTATE, "Failed to save state - error writing %s", filename.c_str()); - gui_display_notification("Error saving state", 5000); + os_notify("Error saving state", 5000); if (zipFile.rawFile() != nullptr) zipFile.Close(); else @@ -193,7 +193,7 @@ void dc_loadstate(int index) if (f == nullptr) { WARN_LOG(SAVESTATE, "Failed to load state - could not open %s for reading", filename.c_str()); - gui_display_notification("Save state not found", 2000); + os_notify("Save state not found", 2000); return; } SavestateHeader header; @@ -233,7 +233,7 @@ void dc_loadstate(int index) if (data == nullptr) { WARN_LOG(SAVESTATE, "Failed to load state - could not malloc %d bytes", total_size); - gui_display_notification("Failed to load state - memory full", 5000); + os_notify("Failed to load state - memory full", 5000); if (zipFile.rawFile() == nullptr) std::fclose(f); else @@ -255,7 +255,7 @@ void dc_loadstate(int index) if (read_size != total_size) { WARN_LOG(SAVESTATE, "Failed to load state - I/O error"); - gui_display_notification("Failed to load state - I/O error", 5000); + os_notify("Failed to load state - I/O error", 5000); free(data); return; } diff --git a/core/oslib/oslib.h b/core/oslib/oslib.h index 8c64fd4df..985f6cb77 100644 --- a/core/oslib/oslib.h +++ b/core/oslib/oslib.h @@ -15,6 +15,7 @@ void os_InstallFaultHandler(); void os_UninstallFaultHandler(); void os_RunInstance(int argc, const char *argv[]); void os_SetThreadName(const char *name); +void os_notify(const char *msg, int durationMs = 2000, const char *details = nullptr); // raii thread name setter class ThreadName diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 49d717644..4ed506251 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -3033,16 +3033,16 @@ static void gui_display_settings() ImGui::End(); } -void gui_display_notification(const char *msg, int duration, const char *details) +void os_notify(const char *msg, int durationMs, const char *details) { if (gui_state != GuiState::Closed) { std::lock_guard _{osd_message_mutex}; osd_message = msg; - osd_message_end = getTimeMs() + duration; + osd_message_end = getTimeMs() + durationMs; } else { - toast.show(msg, details != nullptr ? details : "", duration); + toast.show(msg, details != nullptr ? details : "", durationMs); } } @@ -3630,7 +3630,7 @@ void fatal_error(const char* text, ...) va_end(args); ERROR_LOG(COMMON, "%s", temp); - gui_display_notification("Fatal Error", 20000, temp); + os_notify("Fatal Error", 20000, temp); } extern bool subfolders_read; @@ -3727,15 +3727,15 @@ void gui_takeScreenshot() std::vector data; getScreenshot(data); if (data.empty()) { - gui_display_notification("No screenshot available", 2000); + os_notify("No screenshot available", 2000); } else { try { hostfs::saveScreenshot(name, data); - gui_display_notification("Screenshot saved", 2000, name.c_str()); + os_notify("Screenshot saved", 2000, name.c_str()); } catch (const FlycastException& e) { - gui_display_notification("Error saving screenshot", 5000, e.what()); + os_notify("Error saving screenshot", 5000, e.what()); } } }); diff --git a/core/ui/gui.h b/core/ui/gui.h index ced181f0e..9c23c38b0 100644 --- a/core/ui/gui.h +++ b/core/ui/gui.h @@ -25,7 +25,6 @@ void gui_init(); void gui_initFonts(); void gui_open_settings(); void gui_display_ui(); -void gui_display_notification(const char *msg, int duration, const char *details = nullptr); void gui_draw_osd(); void gui_display_osd(); void gui_display_profiler(); diff --git a/shell/libretro/libretro.cpp b/shell/libretro/libretro.cpp index 4348b6748..ca2c8209f 100644 --- a/shell/libretro/libretro.cpp +++ b/shell/libretro/libretro.cpp @@ -64,6 +64,7 @@ #include "cfg/option.h" #include "version.h" #include "rend/transform_matrix.h" +#include "oslib/oslib.h" constexpr char slash = path_default_slash_c(); @@ -196,7 +197,6 @@ static retro_rumble_interface rumble; static void refresh_devices(bool first_startup); static void init_disk_control_interface(); static bool read_m3u(const char *file); -void gui_display_notification(const char *msg, int duration, const char *details = nullptr); static void updateVibration(u32 port, float power, float inclination, u32 durationMs); static std::string game_data; @@ -1189,7 +1189,7 @@ void retro_run() } } catch (const FlycastException& e) { ERROR_LOG(COMMON, "%s", e.what()); - gui_display_notification(e.what(), 5000); + os_notify(e.what(), 5000); environ_cb(RETRO_ENVIRONMENT_SHUTDOWN, NULL); } @@ -1214,7 +1214,7 @@ static bool loadGame() emu.loadGame(game_data.c_str()); } catch (const FlycastException& e) { ERROR_LOG(BOOT, "%s", e.what()); - gui_display_notification(e.what(), 5000); + os_notify(e.what(), 5000); retro_unload_game(); return false; } @@ -2031,7 +2031,7 @@ bool retro_load_game(const struct retro_game_info *game) if (environ_cb(RETRO_ENVIRONMENT_GET_JIT_CAPABLE, &can_jit) && !can_jit) { // jit is required both for performance and for audio. trying to run // without the jit will cause a crash. - gui_display_notification("Cannot run without JIT", 5000); + os_notify("Cannot run without JIT", 5000); return false; } #endif @@ -3702,10 +3702,10 @@ static bool read_m3u(const char *file) return disk_index != 0; } -void gui_display_notification(const char *msg, int duration, const char *details) +void os_notify(const char *msg, int durationMs, const char *details) { retro_message retromsg; retromsg.msg = msg; - retromsg.frames = duration / 17; + retromsg.frames = durationMs / 17; environ_cb(RETRO_ENVIRONMENT_SET_MESSAGE, &retromsg); } From 34216f77af086abdb9b85cdec2e16acff32a09a9 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 15 May 2024 11:55:06 +0200 Subject: [PATCH 74/86] naomi: fix marstv freeze Force JP BIOS version D for this game. Issue #1508 --- core/hw/naomi/naomi_cart.cpp | 3 ++- core/hw/naomi/naomi_roms.cpp | 52 +++++++++++++++++++++--------------- core/hw/naomi/naomi_roms.h | 1 + 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/core/hw/naomi/naomi_cart.cpp b/core/hw/naomi/naomi_cart.cpp index ce9af41d0..588def40e 100644 --- a/core/hw/naomi/naomi_cart.cpp +++ b/core/hw/naomi/naomi_cart.cpp @@ -68,7 +68,7 @@ static bool loadBios(const char *filename, Archive *child_archive, Archive *pare const BIOS_t *bios = &BIOS[biosid]; - std::string arch_name(filename); + std::string arch_name(bios->filename != nullptr ? bios->filename : filename); std::string path = hostfs::findNaomiBios(arch_name + ".zip"); if (path.empty()) path = hostfs::findNaomiBios(arch_name + ".7z"); @@ -123,6 +123,7 @@ static bool loadBios(const char *filename, Archive *child_archive, Archive *pare break; case EepromBE16: { + // FIXME memory leak naomi_default_eeprom = (u8 *)malloc(bios->blobs[romid].length); if (naomi_default_eeprom == nullptr) throw NaomiCartException("Memory allocation failed"); diff --git a/core/hw/naomi/naomi_roms.cpp b/core/hw/naomi/naomi_roms.cpp index d7c5fc9ab..a2b092a16 100644 --- a/core/hw/naomi/naomi_roms.cpp +++ b/core/hw/naomi/naomi_roms.cpp @@ -88,51 +88,51 @@ const BIOS_t BIOS[] = //ROM_SYSTEM_BIOS( 1, "bios1", "epr-21576g (Japan)" ) { 0, "epr-21576g.ic27", 0x000000, 0x200000, 0xd2a1c6bf }, //ROM_SYSTEM_BIOS( 2, "bios2", "epr-21576e (Japan)" ) - //{ 0, "epr-21576e.ic27", 0x000000, 0x200000 }, + //{ 0, "epr-21576e.ic27", 0x000000, 0x200000, 0x08c0add7 }, //ROM_SYSTEM_BIOS( 3, "bios3", "epr-21576d (Japan)" ) - //{ 0, "epr-21576d.ic27", 0x000000, 0x200000 }, + //{ 0, "epr-21576d.ic27", 0x000000, 0x200000, 0x3b2afa7b }, //ROM_SYSTEM_BIOS( 4, "bios4", "epr-21576c (Japan)" ) - //{ 0, "epr-21576c.ic27", 0x000000, 0x200000 }, // BAD DUMP + //{ 0, "epr-21576c.ic27", 0x000000, 0x200000, 0x4599ad13 }, // BAD DUMP //ROM_SYSTEM_BIOS( 5, "bios5", "epr-21576b (Japan)" ) - //{ 0, "epr-21576b.ic27", 0x000000, 0x200000 }, + //{ 0, "epr-21576b.ic27", 0x000000, 0x200000, 0x755a6e07 }, //ROM_SYSTEM_BIOS( 6, "bios6", "epr-21576a (Japan)" ) - //{ 0, "epr-21576a.ic27", 0x000000, 0x200000 }, + //{ 0, "epr-21576a.ic27", 0x000000, 0x200000, 0xcedfe439 }, //ROM_SYSTEM_BIOS( 7, "bios7", "epr-21576 (Japan)" ) - //{ 0, "epr-21576.ic27", 0x000000, 0x200000 }, + //{ 0, "epr-21576.ic27", 0x000000, 0x200000, 0x9dad3495 }, //ROM_SYSTEM_BIOS( 8, "bios8", "epr-21578h (Export)" ) { 2, "epr-21578h.ic27", 0x000000, 0x200000, 0x7b452946 }, //ROM_SYSTEM_BIOS( 9, "bios9", "epr-21578g (Export)" ) { 2, "epr-21578g.ic27", 0x000000, 0x200000, 0x55413214 }, //ROM_SYSTEM_BIOS( 10, "bios10", "epr-21578f (Export)" ) - //{ 2, "epr-21578f.ic27", 0x000000, 0x200000 }, + //{ 2, "epr-21578f.ic27", 0x000000, 0x200000, 0x628a27fd }, //ROM_SYSTEM_BIOS( 11, "bios11", "epr-21578e (Export)" ) - //{ 2, "epr-21578e.ic27", 0x000000, 0x200000 }, + //{ 2, "epr-21578e.ic27", 0x000000, 0x200000, 0x087f09a3 }, //ROM_SYSTEM_BIOS( 12, "bios12", "epr-21578d (Export)" ) - //{ 2, "epr-21578d.ic27", 0x000000, 0x200000 }, + //{ 2, "epr-21578d.ic27", 0x000000, 0x200000, 0xdfd5f42a }, //ROM_SYSTEM_BIOS( 13, "bios13", "epr-21578a (Export)" ) - //{ 2, "epr-21578a.ic27", 0x000000, 0x200000 }, + //{ 2, "epr-21578a.ic27", 0x000000, 0x200000, 0x6c9aad83 }, //ROM_SYSTEM_BIOS( 14, "bios14", "epr-21577h (USA)" ) { 1, "epr-21577h.ic27", 0x000000, 0x200000, 0xfdf17452 }, //ROM_SYSTEM_BIOS( 15, "bios15", "epr-21577g (USA)" ) { 1, "epr-21577g.ic27", 0x000000, 0x200000, 0x25f64af7 }, //ROM_SYSTEM_BIOS( 16, "bios16", "epr-21577e (USA)" ) - //{ 1, "epr-21577e.ic27", 0x000000, 0x200000 }, + //{ 1, "epr-21577e.ic27", 0x000000, 0x200000, 0xcf36e97b }, //ROM_SYSTEM_BIOS( 17, "bios17", "epr-21577d (USA)" ) - //{ 1, "epr-21577d.ic27", 0x000000, 0x200000 }, + //{ 1, "epr-21577d.ic27", 0x000000, 0x200000, 0x60ddcbbe }, //ROM_SYSTEM_BIOS( 18, "bios18", "epr-21577a (USA)" ) - //{ 1, "epr-21577a.ic27", 0x000000, 0x200000 }, + //{ 1, "epr-21577a.ic27", 0x000000, 0x200000, 0x969dc491 }, //ROM_SYSTEM_BIOS( 19, "bios19", "epr-21579d (Korea)" ) { 3, "epr-21579d.ic27", 0x000000, 0x200000, 0x33513691 }, //ROM_SYSTEM_BIOS( 20, "bios20", "epr-21579 (Korea)" ) - //{ 3, "epr-21579.ic27", 0x000000, 0x200000 }, + //{ 3, "epr-21579.ic27", 0x000000, 0x200000, 0x71f9c918 }, //ROM_SYSTEM_BIOS( 21, "bios21", "Set4 Dev BIOS" ) - //{ 3, "boot_rom_64b8.ic606", 0x000000, 0x080000 }, + //{ 3, "boot_rom_64b8.ic606", 0x000000, 0x080000, 0x7a50fab9 }, //ROM_SYSTEM_BIOS( 22, "bios22", "Dev BIOS v1.10" ) - //{ 3, "develop110.ic27", 0x000000, 0x200000 }, + //{ 3, "develop110.ic27", 0x000000, 0x200000, 0xde7cfdb0 }, //ROM_SYSTEM_BIOS( 23, "bios23", "Dev BIOS (Nov 1998)" ) - //{ 3, "develop.ic27", 0x000000, 0x200000 }, + //{ 3, "develop.ic27", 0x000000, 0x200000, 0x309a196a }, //ROM_SYSTEM_BIOS( 24, "bios24", "Development ROM Board" ) - //{ 3, "zukinver0930.ic25", 0x000000, 0x200000 }, + //{ 3, "zukinver0930.ic25", 0x000000, 0x200000, 0x58e17c23 }, //ROM_SYSTEM_BIOS( 25, "bios25", "epr-21576h (multi-region hack)" ) // The default dipswitch configuration selects Korea for the multiregion hacked BIOS // See hw/maple/maple_jvs.cpp @@ -176,6 +176,17 @@ const BIOS_t BIOS[] = { 2, "mb_eeprom_exp.ic54s", 0x000, 0x800, 0x947ddfad, EepromBE16 }, }, }, + { + "naomi-jp-d", // for marstv + { + //ROM_SYSTEM_BIOS( 3, "bios3", "epr-21576d (Japan)" ) + { 0, "epr-21576d.ic27", 0x000000, 0x200000, 0x3b2afa7b }, + { 1, "epr-21576d.ic27", 0x000000, 0x200000, 0x3b2afa7b }, + { 2, "epr-21576d.ic27", 0x000000, 0x200000, 0x3b2afa7b }, + { 3, "epr-21576d.ic27", 0x000000, 0x200000, 0x3b2afa7b }, + }, + "naomi", + }, { nullptr } @@ -2375,11 +2386,11 @@ const Game Games[] = // Mars TV (JPN) { "marstv", - NULL, + nullptr, "Mars TV", 0x08000000, 0x280b8ef5, - NULL, + "naomi-jp-d", M2, ROT0, { @@ -2400,7 +2411,6 @@ const Game Games[] = { "mpr-22990.ic13s", 0x6800000, 0x800000 }, { "mpr-22991.ic14s", 0x7000000, 0x800000 }, { "mpr-22992.ic15s", 0x7800000, 0x800000 }, - { NULL, 0, 0 }, } }, // Mazan: Flash of the Blade (MAZ2 Ver. A) diff --git a/core/hw/naomi/naomi_roms.h b/core/hw/naomi/naomi_roms.h index 3277a32eb..c67f78692 100644 --- a/core/hw/naomi/naomi_roms.h +++ b/core/hw/naomi/naomi_roms.h @@ -62,6 +62,7 @@ struct BIOS_t u32 crc; BlobType blob_type; } blobs[MAX_GAME_FILES]; + const char* filename; // if different from name }; extern const BIOS_t BIOS[]; From 5755c5b3c6c2d08e3660e38380be996b8727dd66 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 15 May 2024 12:33:06 +0200 Subject: [PATCH 75/86] fix compile warning --- core/network/picoppp.cpp | 42 ++++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/core/network/picoppp.cpp b/core/network/picoppp.cpp index 7ba626a94..b6037d42a 100644 --- a/core/network/picoppp.cpp +++ b/core/network/picoppp.cpp @@ -745,38 +745,34 @@ static void check_dns_entries() if (public_ip.addr == 0) { - if (!dns_query_start) + u32 ip; + pico_string_to_ipv4(RESOLVER1_OPENDNS_COM, &ip); + pico_ip4 tmpdns { ip }; + if (dns_query_start == 0) { dns_query_start = PICO_TIME_MS(); - struct pico_ip4 tmpdns; - pico_string_to_ipv4(RESOLVER1_OPENDNS_COM, &tmpdns.addr); get_host_by_name("myip.opendns.com", tmpdns); } + else if (get_dns_answer(&public_ip, tmpdns) == 0) + { + dns_query_attempts = 0; + dns_query_start = 0; + char myip[16]; + pico_ipv4_to_string(myip, public_ip.addr); + INFO_LOG(MODEM, "My IP is %s", myip); + } else { - struct pico_ip4 tmpdns; - pico_string_to_ipv4(RESOLVER1_OPENDNS_COM, &tmpdns.addr); - if (get_dns_answer(&public_ip, tmpdns) == 0) + if (PICO_TIME_MS() - dns_query_start > 1000) { - dns_query_attempts = 0; - dns_query_start = 0; - char myip[16]; - pico_ipv4_to_string(myip, public_ip.addr); - INFO_LOG(MODEM, "My IP is %s", myip); - } - else - { - if (PICO_TIME_MS() - dns_query_start > 1000) + if (++dns_query_attempts >= 5) { - if (++dns_query_attempts >= 5) - { - public_ip.addr = 0xffffffff; // Bogus but not null - dns_query_attempts = 0; - } - else - // Retry - dns_query_start = 0; + public_ip.addr = 0xffffffff; // Bogus but not null + dns_query_attempts = 0; } + else + // Retry + dns_query_start = 0; } } } From cc56b0fbaef0a818a2f9a361fc1f59e3c5d68abf Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 15 May 2024 14:01:58 +0200 Subject: [PATCH 76/86] vk: multi render support Fixes Quake III split screen vs. and MSR rear view mirror. Issue #1511 --- core/rend/vulkan/commandpool.cpp | 35 ++++-- core/rend/vulkan/commandpool.h | 7 +- core/rend/vulkan/drawer.cpp | 66 +++++----- core/rend/vulkan/drawer.h | 74 +++++++---- core/rend/vulkan/oit/oit_drawer.cpp | 160 +++++++++++------------- core/rend/vulkan/oit/oit_drawer.h | 83 ++++-------- core/rend/vulkan/oit/oit_pipeline.h | 7 +- core/rend/vulkan/oit/oit_renderer.cpp | 11 +- core/rend/vulkan/oit/oit_renderpass.cpp | 5 +- core/rend/vulkan/oit/oit_renderpass.h | 24 ++-- core/rend/vulkan/pipeline.h | 4 +- core/rend/vulkan/vk_context_lr.cpp | 2 +- core/rend/vulkan/vulkan.h | 10 +- core/rend/vulkan/vulkan_renderer.cpp | 15 ++- 14 files changed, 269 insertions(+), 234 deletions(-) diff --git a/core/rend/vulkan/commandpool.cpp b/core/rend/vulkan/commandpool.cpp index c9424aa62..587bb1cfc 100644 --- a/core/rend/vulkan/commandpool.cpp +++ b/core/rend/vulkan/commandpool.cpp @@ -22,6 +22,7 @@ void CommandPool::Init(size_t chainSize) { this->chainSize = chainSize; + device = VulkanContext::Instance()->GetDevice(); if (commandPools.size() > chainSize) { commandPools.resize(chainSize); @@ -31,9 +32,9 @@ void CommandPool::Init(size_t chainSize) { while (commandPools.size() < chainSize) { - commandPools.push_back(VulkanContext::Instance()->GetDevice().createCommandPoolUnique( + commandPools.push_back(device.createCommandPoolUnique( vk::CommandPoolCreateInfo(vk::CommandPoolCreateFlagBits::eTransient, VulkanContext::Instance()->GetGraphicsQueueFamilyIndex()))); - fences.push_back(VulkanContext::Instance()->GetDevice().createFenceUnique(vk::FenceCreateInfo(vk::FenceCreateFlagBits::eSignaled))); + fences.push_back(device.createFenceUnique(vk::FenceCreateInfo(vk::FenceCreateFlagBits::eSignaled))); } } freeBuffers.resize(chainSize); @@ -46,7 +47,7 @@ void CommandPool::Term() if (!fences.empty()) { std::vector allFences = vk::uniqueToRaw(fences); - vk::Result res = VulkanContext::Instance()->GetDevice().waitForFences(allFences, true, UINT64_MAX); + vk::Result res = device.waitForFences(allFences, true, UINT64_MAX); if (res != vk::Result::eSuccess) WARN_LOG(RENDERER, "CommandPool::Term: waitForFences failed %d", (int)res); } @@ -63,15 +64,16 @@ void CommandPool::BeginFrame() return; frameStarted = true; index = (index + 1) % chainSize; - vk::Result res = VulkanContext::Instance()->GetDevice().waitForFences(fences[index].get(), true, UINT64_MAX); + vk::Result res = device.waitForFences(fences[index].get(), true, UINT64_MAX); if (res != vk::Result::eSuccess) WARN_LOG(RENDERER, "CommandPool::BeginFrame: waitForFences failed %d", (int)res); std::vector& inFlightBuf = inFlightBuffers[index]; std::vector& freeBuf = freeBuffers[index]; std::move(inFlightBuf.begin(), inFlightBuf.end(), std::back_inserter(freeBuf)); inFlightBuf.clear(); - VulkanContext::Instance()->GetDevice().resetCommandPool(*commandPools[index], vk::CommandPoolResetFlagBits::eReleaseResources); + device.resetCommandPool(*commandPools[index], vk::CommandPoolResetFlagBits::eReleaseResources); inFlightObjects[index].clear(); + lastBuffers.clear(); } void CommandPool::EndFrame() @@ -80,16 +82,30 @@ void CommandPool::EndFrame() return; frameStarted = false; std::vector commandBuffers = vk::uniqueToRaw(inFlightBuffers[index]); - VulkanContext::Instance()->GetDevice().resetFences(fences[index].get()); + if (!commandBuffers.empty()) + { + // sort buffers: !last, last + size_t len = commandBuffers.size() - 1; + while (len != 0) + { + for (size_t i = 0; i < len; i++) + if (lastBuffers[i] && !lastBuffers[i + 1]) { + std::swap(lastBuffers[i], lastBuffers[i + 1]); + std::swap(commandBuffers[i], commandBuffers[i + 1]); + } + len--; + } + } + device.resetFences(fences[index].get()); VulkanContext::Instance()->SubmitCommandBuffers(commandBuffers, *fences[index]); } -vk::CommandBuffer CommandPool::Allocate() +vk::CommandBuffer CommandPool::Allocate(bool submitLast) { if (freeBuffers[index].empty()) { inFlightBuffers[index].emplace_back(std::move( - VulkanContext::Instance()->GetDevice().allocateCommandBuffersUnique(vk::CommandBufferAllocateInfo(*commandPools[index], vk::CommandBufferLevel::ePrimary, 1)) + device.allocateCommandBuffersUnique(vk::CommandBufferAllocateInfo(*commandPools[index], vk::CommandBufferLevel::ePrimary, 1)) .front())); } else @@ -97,13 +113,14 @@ vk::CommandBuffer CommandPool::Allocate() inFlightBuffers[index].emplace_back(std::move(freeBuffers[index].back())); freeBuffers[index].pop_back(); } + lastBuffers.push_back(submitLast); return *inFlightBuffers[index].back(); } void CommandPool::EndFrameAndWait() { EndFrame(); - vk::Result res = VulkanContext::Instance()->GetDevice().waitForFences(fences[index].get(), true, UINT64_MAX); + vk::Result res = device.waitForFences(fences[index].get(), true, UINT64_MAX); if (res != vk::Result::eSuccess) WARN_LOG(RENDERER, "CommandPool::waitForCommandCompletion: waitForFences failed %d", (int)res); inFlightObjects[index].clear(); diff --git a/core/rend/vulkan/commandpool.h b/core/rend/vulkan/commandpool.h index 3a56d623d..3f42f662a 100644 --- a/core/rend/vulkan/commandpool.h +++ b/core/rend/vulkan/commandpool.h @@ -32,10 +32,9 @@ public: void BeginFrame(); void EndFrame(); void EndFrameAndWait(); - vk::CommandBuffer Allocate(); + vk::CommandBuffer Allocate(bool submitLast = false); - int GetIndex() const - { + int GetIndex() const { return index; } @@ -47,10 +46,12 @@ private: int index = 0; std::vector> freeBuffers; std::vector> inFlightBuffers; + std::vector lastBuffers; std::vector commandPools; std::vector fences; // size should be the same as used by client: 2 for renderer (texCommandPool) size_t chainSize; std::vector>> inFlightObjects; bool frameStarted = false; + vk::Device device{}; }; diff --git a/core/rend/vulkan/drawer.cpp b/core/rend/vulkan/drawer.cpp index 1b6cc7a35..609698545 100644 --- a/core/rend/vulkan/drawer.cpp +++ b/core/rend/vulkan/drawer.cpp @@ -229,7 +229,7 @@ void Drawer::DrawPoly(const vk::CommandBuffer& cmdBuffer, u32 listType, bool sor break; } } - descriptorSets.bindPerPolyDescriptorSets(cmdBuffer, poly, index, *GetMainBuffer(0)->buffer, offset, offsets.lightsOffset, + descriptorSets.bindPerPolyDescriptorSets(cmdBuffer, poly, index, curMainBuffer, offset, offsets.lightsOffset, listType == ListType_Punch_Through); } cmdBuffer.drawIndexed(count, 1, first, 0, 0); @@ -278,8 +278,7 @@ void Drawer::DrawModVols(const vk::CommandBuffer& cmdBuffer, int first, int coun if (count == 0 || pvrrc.modtrig.empty() || !config::ModifierVolumes) return; - vk::Buffer buffer = GetMainBuffer(0)->buffer.get(); - cmdBuffer.bindVertexBuffers(0, buffer, offsets.modVolOffset); + cmdBuffer.bindVertexBuffers(0, curMainBuffer, offsets.modVolOffset); SetScissor(cmdBuffer, baseScissor); ModifierVolumeParam* params = &pvrrc.global_param_mvo[first]; @@ -305,7 +304,7 @@ void Drawer::DrawModVols(const vk::CommandBuffer& cmdBuffer, int first, int coun pipeline = pipelineManager->GetModifierVolumePipeline(ModVolMode::Xor, param.isp.CullMode, param.isNaomi2()); // XOR'ing (closed volume) cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); - descriptorSets.bindPerPolyDescriptorSets(cmdBuffer, param, first + cmv, *GetMainBuffer(0)->buffer, offsets.naomi2ModVolOffset); + descriptorSets.bindPerPolyDescriptorSets(cmdBuffer, param, first + cmv, curMainBuffer, offsets.naomi2ModVolOffset); cmdBuffer.draw(param.count * 3, 1, param.first * 3, 0); @@ -318,7 +317,7 @@ void Drawer::DrawModVols(const vk::CommandBuffer& cmdBuffer, int first, int coun mod_base = -1; } } - cmdBuffer.bindVertexBuffers(0, buffer, {0}); + cmdBuffer.bindVertexBuffers(0, curMainBuffer, {0}); std::array pushConstants = { 1 - FPU_SHAD_SCALE.scale_factor / 256.f, 0, 0, 0, 0 }; cmdBuffer.pushConstants(pipelineManager->GetPipelineLayout(), vk::ShaderStageFlagBits::eFragment, 0, pushConstants); @@ -351,6 +350,7 @@ void Drawer::UploadMainBuffer(const VertexShaderUniforms& vertexUniforms, const BufferData *buffer = GetMainBuffer(packer.size()); packer.upload(*buffer); + curMainBuffer = buffer->buffer.get(); } bool Drawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) @@ -393,14 +393,13 @@ bool Drawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) UploadMainBuffer(vtxUniforms, fragUniforms); // Update per-frame descriptor set and bind it - descriptorSets.updateUniforms(GetMainBuffer(0)->buffer.get(), (u32)offsets.vertexUniformOffset, (u32)offsets.fragmentUniformOffset, + descriptorSets.updateUniforms(curMainBuffer, (u32)offsets.vertexUniformOffset, (u32)offsets.fragmentUniformOffset, fogTexture->GetImageView(), paletteTexture->GetImageView()); descriptorSets.bindPerFrameDescriptorSets(cmdBuffer); // Bind vertex and index buffers - const vk::Buffer buffer = GetMainBuffer(0)->buffer.get(); - cmdBuffer.bindVertexBuffers(0, buffer, {0}); - cmdBuffer.bindIndexBuffer(buffer, offsets.indexOffset, vk::IndexType::eUint32); + cmdBuffer.bindVertexBuffers(0, curMainBuffer, {0}); + cmdBuffer.bindIndexBuffer(curMainBuffer, offsets.indexOffset, vk::IndexType::eUint32); // Make sure to push constants even if not used std::array pushConstants = { 0, 0, 0, 0, 0 }; @@ -430,6 +429,7 @@ bool Drawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) DrawList(cmdBuffer, ListType_Translucent, false, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count); previous_pass = current_pass; } + curMainBuffer = nullptr; return !pvrrc.isRTT; } @@ -464,7 +464,7 @@ vk::CommandBuffer TextureDrawer::BeginRenderPass() vk::Device device = context->GetDevice(); NewImage(); - vk::CommandBuffer commandBuffer = commandPool->Allocate(); + vk::CommandBuffer commandBuffer = commandPool->Allocate(true); commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); if (!depthAttachment || widthPow2 > depthAttachment->getExtent().width || heightPow2 > depthAttachment->getExtent().height) @@ -614,7 +614,6 @@ void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderMan depthAttachment.reset(); transitionNeeded.clear(); clearNeeded.clear(); - frameRendered = false; } this->viewport = viewport; if (!depthAttachment) @@ -707,36 +706,43 @@ void ScreenDrawer::Init(SamplerManager *samplerManager, ShaderManager *shaderMan vk::CommandBuffer ScreenDrawer::BeginRenderPass() { - NewImage(); - vk::CommandBuffer commandBuffer = commandPool->Allocate(); - commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - - if (transitionNeeded[GetCurrentImage()]) + if (!renderPassStarted) { - setImageLayout(commandBuffer, colorAttachments[GetCurrentImage()]->GetImage(), vk::Format::eR8G8B8A8Unorm, - 1, vk::ImageLayout::eUndefined, - config::EmulateFramebuffer ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal); - transitionNeeded[GetCurrentImage()] = false; - } + NewImage(); + frameRendered = false; + vk::CommandBuffer commandBuffer = commandPool->Allocate(true); + commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - vk::RenderPass renderPass = clearNeeded[GetCurrentImage()] || pvrrc.clearFramebuffer ? *renderPassClear : *renderPassLoad; - clearNeeded[GetCurrentImage()] = false; - const std::array clear_colors = { vk::ClearColorValue(std::array { 0.f, 0.f, 0.f, 1.f }), vk::ClearDepthStencilValue { 0.f, 0 } }; - commandBuffer.beginRenderPass(vk::RenderPassBeginInfo(renderPass, *framebuffers[GetCurrentImage()], - vk::Rect2D( { 0, 0 }, viewport), clear_colors), vk::SubpassContents::eInline); - commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, (float)viewport.width, (float)viewport.height, 1.0f, 0.0f)); + if (transitionNeeded[GetCurrentImage()]) + { + setImageLayout(commandBuffer, colorAttachments[GetCurrentImage()]->GetImage(), vk::Format::eR8G8B8A8Unorm, + 1, vk::ImageLayout::eUndefined, + config::EmulateFramebuffer ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal); + transitionNeeded[GetCurrentImage()] = false; + } + + vk::RenderPass renderPass = clearNeeded[GetCurrentImage()] || pvrrc.clearFramebuffer ? *renderPassClear : *renderPassLoad; + clearNeeded[GetCurrentImage()] = false; + const std::array clear_colors = { vk::ClearColorValue(std::array { 0.f, 0.f, 0.f, 1.f }), vk::ClearDepthStencilValue { 0.f, 0 } }; + commandBuffer.beginRenderPass(vk::RenderPassBeginInfo(renderPass, *framebuffers[GetCurrentImage()], + vk::Rect2D( { 0, 0 }, viewport), clear_colors), vk::SubpassContents::eInline); + currentCommandBuffer = commandBuffer; + renderPassStarted = true; + } + currentCommandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, (float)viewport.width, (float)viewport.height, 1.0f, 0.0f)); matrices.CalcMatrices(&pvrrc, viewport.width, viewport.height); SetBaseScissor(viewport); - commandBuffer.setScissor(0, baseScissor); - currentCommandBuffer = commandBuffer; + currentCommandBuffer.setScissor(0, baseScissor); - return commandBuffer; + return currentCommandBuffer; } void ScreenDrawer::EndRenderPass() { + if (!renderPassStarted) + return; currentCommandBuffer.endRenderPass(); if (config::EmulateFramebuffer) { diff --git a/core/rend/vulkan/drawer.h b/core/rend/vulkan/drawer.h index eb8986220..a22867ed8 100644 --- a/core/rend/vulkan/drawer.h +++ b/core/rend/vulkan/drawer.h @@ -52,6 +52,48 @@ protected: } } + BufferData* GetMainBuffer(u32 size, vk::BufferUsageFlags extraFlags = {}) + { + const vk::BufferUsageFlags usageFlags + { vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eUniformBuffer | extraFlags }; + BufferData *buffer; + if (!mainBuffers.empty()) + { + buffer = mainBuffers.back().release(); + mainBuffers.pop_back(); + if (buffer->bufferSize < size) + { + // FIXME vf4evob still complains about buffer in use after 2 frames. Due to swap chain size of 3 + commandPool->addToFlight(new Deleter(buffer)); + u32 newSize = (u32)buffer->bufferSize; + while (newSize < size) + newSize *= 2; + INFO_LOG(RENDERER, "Increasing main buffer size %zd -> %d", buffer->bufferSize, newSize); + buffer = new BufferData(newSize, usageFlags); + } + } + else { + buffer = new BufferData(std::max(512 * 1024u, size), usageFlags); + } + + class BufferHolder : public Deletable + { + public: + BufferHolder(BufferData *buffer, BaseDrawer *drawer) : buffer(buffer), drawer(drawer) {} + + ~BufferHolder() override { + drawer->mainBuffers.emplace_back(buffer); + } + + private: + BufferData *buffer; + BaseDrawer *drawer; + }; + commandPool->addToFlight(new BufferHolder(buffer, this)); + + return buffer; + } + template T MakeFragmentUniforms() { @@ -167,6 +209,7 @@ protected: vk::Rect2D currentScissor; TransformMatrix matrices; CommandPool *commandPool = nullptr; + std::vector> mainBuffers; }; class Drawer : public BaseDrawer @@ -181,7 +224,9 @@ public: } bool Draw(const Texture *fogTexture, const Texture *paletteTexture); - virtual void EndRenderPass() { renderPass++; } + virtual void EndRenderPass() { + renderPassStarted = false; + } vk::CommandBuffer GetCurrentCommandBuffer() const { return currentCommandBuffer; } protected: @@ -196,7 +241,6 @@ protected: perStripSorting = config::PerStripSorting; pipelineManager->Reset(); } - renderPass = 0; } void Init(SamplerManager *samplerManager, PipelineManager *pipelineManager) @@ -209,29 +253,9 @@ protected: int GetCurrentImage() const { return imageIndex; } - BufferData* GetMainBuffer(u32 size) - { - u32 bufferIndex = imageIndex + renderPass * GetSwapChainSize(); - while (mainBuffers.size() <= bufferIndex) - { - mainBuffers.push_back(std::make_unique(std::max(512 * 1024u, size), - vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eUniformBuffer)); - } - if (mainBuffers[bufferIndex]->bufferSize < size) - { - u32 newSize = (u32)mainBuffers[bufferIndex]->bufferSize; - while (newSize < size) - newSize *= 2; - INFO_LOG(RENDERER, "Increasing main buffer size %d -> %d", (u32)mainBuffers[bufferIndex]->bufferSize, newSize); - commandPool->addToFlight(new Deleter(mainBuffers[bufferIndex].release())); - mainBuffers[bufferIndex] = std::make_unique(newSize, - vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eUniformBuffer); - } - return mainBuffers[bufferIndex].get(); - } - vk::CommandBuffer currentCommandBuffer; SamplerManager *samplerManager = nullptr; + bool renderPassStarted = false; private: void SortTriangles(); @@ -242,7 +266,6 @@ private: void UploadMainBuffer(const VertexShaderUniforms& vertexUniforms, const FragmentShaderUniforms& fragmentUniforms); int imageIndex = 0; - int renderPass = 0; struct { vk::DeviceSize indexOffset = 0; vk::DeviceSize modVolOffset = 0; @@ -256,7 +279,7 @@ private: vk::DeviceSize lightsOffset = 0; } offsets; DescriptorSets descriptorSets; - std::vector> mainBuffers; + vk::Buffer curMainBuffer; PipelineManager *pipelineManager = nullptr; bool perStripSorting = false; bool dithering = false; @@ -282,6 +305,7 @@ public: void EndRenderPass() override; bool PresentFrame() { + EndRenderPass(); if (!frameRendered) return false; frameRendered = false; diff --git a/core/rend/vulkan/oit/oit_drawer.cpp b/core/rend/vulkan/oit/oit_drawer.cpp index ddc538a65..f769f4a87 100644 --- a/core/rend/vulkan/oit/oit_drawer.cpp +++ b/core/rend/vulkan/oit/oit_drawer.cpp @@ -118,7 +118,7 @@ void OITDrawer::DrawPoly(const vk::CommandBuffer& cmdBuffer, u32 listType, bool break; } } - descriptorSets.bindPerPolyDescriptorSets(cmdBuffer, poly, polyNumber, *GetMainBuffer(0)->buffer, offset, offsets.lightsOffset, + descriptorSets.bindPerPolyDescriptorSets(cmdBuffer, poly, polyNumber, curMainBuffer, offset, offsets.lightsOffset, listType == ListType_Punch_Through); } @@ -145,8 +145,7 @@ void OITDrawer::DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int firs if (count == 0 || pvrrc.modtrig.empty() || !config::ModifierVolumes) return; - vk::Buffer buffer = GetMainBuffer(0)->buffer.get(); - cmdBuffer.bindVertexBuffers(0, buffer, offsets.modVolOffset); + cmdBuffer.bindVertexBuffers(0, curMainBuffer, offsets.modVolOffset); SetScissor(cmdBuffer, baseScissor); const ModifierVolumeParam *params = &modVolParams[first]; @@ -187,7 +186,7 @@ void OITDrawer::DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int firs cmdBuffer.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline); vk::DeviceSize uniformOffset = Translucent ? offsets.naomi2TrModVolOffset : offsets.naomi2ModVolOffset; - descriptorSets.bindPerPolyDescriptorSets(cmdBuffer, param, first + cmv, *GetMainBuffer(0)->buffer, uniformOffset); + descriptorSets.bindPerPolyDescriptorSets(cmdBuffer, param, first + cmv, curMainBuffer, uniformOffset); cmdBuffer.draw(param.count * 3, 1, param.first * 3, 0); @@ -215,7 +214,7 @@ void OITDrawer::DrawModifierVolumes(const vk::CommandBuffer& cmdBuffer, int firs } } } - cmdBuffer.bindVertexBuffers(0, buffer, {0}); + cmdBuffer.bindVertexBuffers(0, curMainBuffer, {0}); } void OITDrawer::UploadMainBuffer(const OITDescriptorSets::VertexShaderUniforms& vertexUniforms, @@ -259,6 +258,15 @@ void OITDrawer::UploadMainBuffer(const OITDescriptorSets::VertexShaderUniforms& BufferData *buffer = GetMainBuffer(packer.size()); packer.upload(*buffer); + curMainBuffer = buffer->buffer.get(); +} + +vk::Framebuffer OITTextureDrawer::getFramebuffer(int renderPass, int renderPassCount) +{ + if (renderPass < renderPassCount - 1) + return *tempFramebuffers[(renderPassCount - 1 - renderPass) % 2]; + else + return *framebuffer; } bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) @@ -270,6 +278,7 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) needAttachmentTransition = false; // Not convinced that this is really needed but it makes validation layers happy for (auto& attachment : colorAttachments) + // FIXME should be eTransferSrcOptimal if fullFB (screen) or copy to vram (rtt) -> 1 validation error at startup setImageLayout(cmdBuffer, attachment->GetImage(), vk::Format::eR8G8B8A8Unorm, 1, vk::ImageLayout::eUndefined, vk::ImageLayout::eShaderReadOnlyOptimal); for (auto& attachment : depthAttachments) @@ -322,8 +331,7 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) quadBuffer->Update(); // Update per-frame descriptor set and bind it - const vk::Buffer mainBuffer = GetMainBuffer(0)->buffer.get(); - descriptorSets.updateUniforms(mainBuffer, (u32)offsets.vertexUniformOffset, (u32)offsets.fragmentUniformOffset, + descriptorSets.updateUniforms(curMainBuffer, (u32)offsets.vertexUniformOffset, (u32)offsets.fragmentUniformOffset, fogTexture->GetImageView(), (u32)offsets.polyParamsOffset, (u32)offsets.polyParamsSize, depthAttachments[0]->GetStencilView(), depthAttachments[0]->GetImageView(), paletteTexture->GetImageView(), oitBuffers); @@ -332,8 +340,8 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) descriptorSets.updateColorInputDescSet(1, colorAttachments[1]->GetImageView()); // Bind vertex and index buffers - cmdBuffer.bindVertexBuffers(0, mainBuffer, {0}); - cmdBuffer.bindIndexBuffer(mainBuffer, offsets.indexOffset, vk::IndexType::eUint32); + cmdBuffer.bindVertexBuffers(0, curMainBuffer, {0}); + cmdBuffer.bindIndexBuffer(curMainBuffer, offsets.indexOffset, vk::IndexType::eUint32); // Make sure to push constants even if not used OITDescriptorSets::PushConstants pushConstants = { }; @@ -368,11 +376,7 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) const bool initialPass = render_pass == 0; const bool finalPass = render_pass == (int)pvrrc.render_passes.size() - 1; - vk::Framebuffer targetFramebuffer; - if (!finalPass) - targetFramebuffer = *tempFramebuffers[(pvrrc.render_passes.size() - 1 - render_pass) % 2]; - else - targetFramebuffer = GetFinalFramebuffer(); + vk::Framebuffer targetFramebuffer = getFramebuffer(render_pass, pvrrc.render_passes.size()); cmdBuffer.beginRenderPass( vk::RenderPassBeginInfo(pipelineManager->GetRenderPass(initialPass, finalPass, initialPass && pvrrc.clearFramebuffer), targetFramebuffer, viewport, clear_colors), @@ -402,11 +406,12 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) // Final subpass cmdBuffer.nextSubpass(vk::SubpassContents::eInline); - descriptorSets.bindColorInputDescSet(cmdBuffer, (pvrrc.render_passes.size() - 1 - render_pass) % 2); + // Bind the input attachment (OP+PT) + descriptorSets.bindColorInputDescSet(cmdBuffer, 1 - getFramebufferIndex()); - if (initialPass && !pvrrc.isRTT && clearNeeded[GetCurrentImage()]) + if (initialPass && !pvrrc.isRTT && clearNeeded[getFramebufferIndex()]) { - clearNeeded[GetCurrentImage()] = false; + clearNeeded[getFramebufferIndex()] = false; SetScissor(cmdBuffer, viewport); cmdBuffer.clearAttachments(vk::ClearAttachment(vk::ImageAspectFlagBits::eColor, 0, clear_colors[0]), vk::ClearRect(viewport, 0, 1)); @@ -443,22 +448,22 @@ bool OITDrawer::Draw(const Texture *fogTexture, const Texture *paletteTexture) if (!finalPass) { // Re-bind vertex and index buffers - cmdBuffer.bindVertexBuffers(0, mainBuffer, {0}); - cmdBuffer.bindIndexBuffer(mainBuffer, offsets.indexOffset, vk::IndexType::eUint32); + cmdBuffer.bindVertexBuffers(0, curMainBuffer, {0}); + cmdBuffer.bindIndexBuffer(curMainBuffer, offsets.indexOffset, vk::IndexType::eUint32); // Tr depth-only pass DrawList(cmdBuffer, ListType_Translucent, current_pass.autosort, Pass::Depth, pvrrc.global_param_tr, previous_pass.tr_count, current_pass.tr_count); - - cmdBuffer.endRenderPass(); } + cmdBuffer.endRenderPass(); previous_pass = current_pass; } + curMainBuffer = nullptr; return !pvrrc.isRTT; } -void OITDrawer::MakeBuffers(int width, int height) +void OITDrawer::MakeBuffers(int width, int height, vk::ImageUsageFlags colorUsage) { oitBuffers->Init(width, height); @@ -467,40 +472,55 @@ void OITDrawer::MakeBuffers(int width, int height) maxWidth = std::max(maxWidth, width); maxHeight = std::max(maxHeight, height); - GetContext()->WaitIdle(); + for (auto& framebuffer : tempFramebuffers) { + if (framebuffer) + commandPool->addToFlight(new Deleter(std::move(framebuffer))); + } + + vk::Device device = GetContext()->GetDevice(); + vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment + | colorUsage; for (auto& attachment : colorAttachments) { - attachment.reset(); + if (attachment) + commandPool->addToFlight(new Deleter(std::move(attachment))); attachment = std::make_unique( - GetContext()->GetPhysicalDevice(), GetContext()->GetDevice()); - attachment->Init(maxWidth, maxHeight, vk::Format::eR8G8B8A8Unorm, - vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eInputAttachment, - "COLOR ATTACHMENT " + std::to_string(colorAttachments.size() - 1)); + GetContext()->GetPhysicalDevice(), device); + attachment->Init(maxWidth, maxHeight, vk::Format::eR8G8B8A8Unorm, usage, + "COLOR ATTACHMENT " + std::to_string(&attachment - &colorAttachments[0])); } for (auto& attachment : depthAttachments) { - attachment.reset(); + if (attachment) + commandPool->addToFlight(new Deleter(std::move(attachment))); attachment = std::make_unique( - GetContext()->GetPhysicalDevice(), GetContext()->GetDevice()); + GetContext()->GetPhysicalDevice(), device); attachment->Init(maxWidth, maxHeight, GetContext()->GetDepthFormat(), vk::ImageUsageFlagBits::eDepthStencilAttachment | vk::ImageUsageFlagBits::eInputAttachment, - "DEPTH ATTACHMENT " + std::to_string(colorAttachments.size() - 1)); + "DEPTH ATTACHMENT" + std::to_string(&attachment - &depthAttachments[0])); } needAttachmentTransition = true; std::array attachments = { - colorAttachments[1]->GetImageView(), colorAttachments[0]->GetImageView(), + colorAttachments[1]->GetImageView(), depthAttachments[0]->GetImageView(), depthAttachments[1]->GetImageView(), }; vk::FramebufferCreateInfo createInfo(vk::FramebufferCreateFlags(), pipelineManager->GetRenderPass(true, true), attachments, maxWidth, maxHeight, 1); - tempFramebuffers[0] = GetContext()->GetDevice().createFramebufferUnique(createInfo); + tempFramebuffers[0] = device.createFramebufferUnique(createInfo); attachments[0] = attachments[1]; - attachments[1] = colorAttachments[1]->GetImageView(); - tempFramebuffers[1] = GetContext()->GetDevice().createFramebufferUnique(createInfo); + attachments[1] = colorAttachments[0]->GetImageView(); + tempFramebuffers[1] = device.createFramebufferUnique(createInfo); +} + +vk::Framebuffer OITScreenDrawer::getFramebuffer(int renderPass, int renderPassCount) +{ + framebufferIndex = 1 - framebufferIndex; + vk::Framebuffer framebuffer = tempFramebuffers[framebufferIndex].get(); + return framebuffer; } void OITScreenDrawer::MakeFramebuffers(const vk::Extent2D& viewport) @@ -509,35 +529,12 @@ void OITScreenDrawer::MakeFramebuffers(const vk::Extent2D& viewport) this->viewport.offset.y = 0; this->viewport.extent = viewport; - MakeBuffers(viewport.width, viewport.height); - framebuffers.clear(); - finalColorAttachments.clear(); - transitionNeeded.clear(); - clearNeeded.clear(); + // make sure all attachments have the same dimensions + maxWidth = 0; + maxHeight = 0; + MakeBuffers(viewport.width, viewport.height, config::EmulateFramebuffer ? vk::ImageUsageFlagBits::eTransferSrc : vk::ImageUsageFlagBits::eSampled); - vk::ImageUsageFlags usage = vk::ImageUsageFlagBits::eColorAttachment; - if (config::EmulateFramebuffer) - usage |= vk::ImageUsageFlagBits::eTransferSrc; - else - usage |= vk::ImageUsageFlagBits::eSampled; - while (finalColorAttachments.size() < GetSwapChainSize()) - { - finalColorAttachments.push_back(std::make_unique( - GetContext()->GetPhysicalDevice(), GetContext()->GetDevice())); - finalColorAttachments.back()->Init(viewport.width, viewport.height, vk::Format::eR8G8B8A8Unorm, - usage, "FINAL ATTACHMENT " + std::to_string(finalColorAttachments.size() - 1)); - std::array attachments = { - finalColorAttachments.back()->GetImageView(), - colorAttachments[0]->GetImageView(), - depthAttachments[0]->GetImageView(), - depthAttachments[1]->GetImageView(), - }; - vk::FramebufferCreateInfo createInfo(vk::FramebufferCreateFlags(), screenPipelineManager->GetRenderPass(true, true), - attachments, viewport.width, viewport.height, 1); - framebuffers.push_back(GetContext()->GetDevice().createFramebufferUnique(createInfo)); - transitionNeeded.push_back(true); - clearNeeded.push_back(true); - } + clearNeeded = { true, true }; } vk::CommandBuffer OITTextureDrawer::NewFrame() @@ -561,10 +558,10 @@ vk::CommandBuffer OITTextureDrawer::NewFrame() VulkanContext *context = GetContext(); vk::Device device = context->GetDevice(); - vk::CommandBuffer commandBuffer = commandPool->Allocate(); + vk::CommandBuffer commandBuffer = commandPool->Allocate(true); commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - MakeBuffers(widthPow2, heightPow2); + MakeBuffers(widthPow2, heightPow2, config::RenderToTextureBuffer ? vk::ImageUsageFlagBits::eTransferSrc : vk::ImageUsageFlagBits::eSampled); vk::ImageView colorImageView; vk::ImageLayout colorImageCurrentLayout; @@ -623,12 +620,13 @@ vk::CommandBuffer OITTextureDrawer::NewFrame() std::array imageViews = { colorImageView, - colorAttachments[0]->GetImageView(), + colorAttachments[1]->GetImageView(), depthAttachments[0]->GetImageView(), depthAttachments[1]->GetImageView(), }; - framebuffers.resize(GetSwapChainSize()); - framebuffers[GetCurrentImage()] = device.createFramebufferUnique(vk::FramebufferCreateInfo(vk::FramebufferCreateFlags(), + if (framebuffer) + commandPool->addToFlight(new Deleter(std::move(framebuffer))); + framebuffer = device.createFramebufferUnique(vk::FramebufferCreateInfo(vk::FramebufferCreateFlags(), rttPipelineManager->GetRenderPass(true, true), imageViews, widthPow2, heightPow2, 1)); commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, (float)upscaledWidth, (float)upscaledHeight, 1.0f, 0.0f)); @@ -644,8 +642,6 @@ vk::CommandBuffer OITTextureDrawer::NewFrame() void OITTextureDrawer::EndFrame() { - currentCommandBuffer.endRenderPass(); - u32 clippedWidth = pvrrc.getFramebufferWidth(); u32 clippedHeight = pvrrc.getFramebufferHeight(); @@ -692,30 +688,24 @@ void OITTextureDrawer::EndFrame() texture->dirty = 0; texture->unprotectVRam(); } - OITDrawer::EndFrame(); } vk::CommandBuffer OITScreenDrawer::NewFrame() { - frameRendered = false; - NewImage(); - vk::CommandBuffer commandBuffer = commandPool->Allocate(); - commandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); - - if (transitionNeeded[GetCurrentImage()]) + if (!frameStarted) { - setImageLayout(commandBuffer, finalColorAttachments[GetCurrentImage()]->GetImage(), vk::Format::eR8G8B8A8Unorm, 1, - vk::ImageLayout::eUndefined, - config::EmulateFramebuffer ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal); - transitionNeeded[GetCurrentImage()] = false; + frameStarted = true; + frameRendered = false; + NewImage(); + currentCommandBuffer = commandPool->Allocate(true); + currentCommandBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); } matrices.CalcMatrices(&pvrrc, viewport.extent.width, viewport.extent.height); SetBaseScissor(viewport.extent); - commandBuffer.setScissor(0, baseScissor); - commandBuffer.setViewport(0, vk::Viewport((float)viewport.offset.x, (float)viewport.offset.y, (float)viewport.extent.width, (float)viewport.extent.height, 1.0f, 0.0f)); - currentCommandBuffer = commandBuffer; + currentCommandBuffer.setScissor(0, baseScissor); + currentCommandBuffer.setViewport(0, vk::Viewport((float)viewport.offset.x, (float)viewport.offset.y, (float)viewport.extent.width, (float)viewport.extent.height, 1.0f, 0.0f)); - return commandBuffer; + return currentCommandBuffer; } diff --git a/core/rend/vulkan/oit/oit_drawer.h b/core/rend/vulkan/oit/oit_drawer.h index 382a43b20..0376127e4 100644 --- a/core/rend/vulkan/oit/oit_drawer.h +++ b/core/rend/vulkan/oit/oit_drawer.h @@ -41,10 +41,9 @@ public: bool Draw(const Texture *fogTexture, const Texture *paletteTexture); virtual vk::CommandBuffer NewFrame() = 0; - virtual void EndFrame() { renderPass++; }; + virtual void EndFrame() = 0; protected: - u32 GetSwapChainSize() { return 2; } void Init(SamplerManager *samplerManager, OITPipelineManager *pipelineManager, OITBuffers *oitBuffers) { this->pipelineManager = pipelineManager; @@ -73,48 +72,26 @@ protected: maxHeight = 0; } - int GetCurrentImage() const { return imageIndex; } - - void NewImage() - { + void NewImage() { descriptorSets.nextFrame(); - imageIndex = (imageIndex + 1) % GetSwapChainSize(); - renderPass = 0; } - BufferData* GetMainBuffer(u32 size) - { - u32 bufferIndex = imageIndex + renderPass * GetSwapChainSize(); - while (mainBuffers.size() <= bufferIndex) - { - mainBuffers.push_back(std::make_unique(std::max(512 * 1024u, size), - vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eUniformBuffer - | vk::BufferUsageFlagBits::eStorageBuffer)); - } - if (mainBuffers[bufferIndex]->bufferSize < size) - { - u32 newSize = (u32)mainBuffers[bufferIndex]->bufferSize; - while (newSize < size) - newSize *= 2; - INFO_LOG(RENDERER, "Increasing main buffer size %d -> %d", (u32)mainBuffers[bufferIndex]->bufferSize, newSize); - // FIXME vf4evob still complains about buffer in use after 2 frames! due to swap chain size of 3 - // even releasing using the vk context doesn't work - commandPool->addToFlight(new Deleter(mainBuffers[bufferIndex].release())); - mainBuffers[bufferIndex] = std::make_unique(newSize, - vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eUniformBuffer - | vk::BufferUsageFlagBits::eStorageBuffer); - } - return mainBuffers[bufferIndex].get(); + BufferData* GetMainBuffer(u32 size) { + return BaseDrawer::GetMainBuffer(size, vk::BufferUsageFlagBits::eStorageBuffer); } - void MakeBuffers(int width, int height); - virtual vk::Framebuffer GetFinalFramebuffer() const = 0; + void MakeBuffers(int width, int height, vk::ImageUsageFlags colorUsage = {}); + virtual vk::Framebuffer getFramebuffer(int renderPass, int renderPassCount) = 0; + virtual int getFramebufferIndex() { return 0; } vk::Rect2D viewport; std::array, 2> colorAttachments; std::array, 2> depthAttachments; + std::array tempFramebuffers; vk::CommandBuffer currentCommandBuffer; std::vector clearNeeded; + int maxWidth = 0; + int maxHeight = 0; private: void DrawPoly(const vk::CommandBuffer& cmdBuffer, u32 listType, bool autosort, Pass pass, @@ -143,18 +120,13 @@ private: std::unique_ptr quadBuffer; - std::array tempFramebuffers; - OITPipelineManager *pipelineManager = nullptr; SamplerManager *samplerManager = nullptr; OITBuffers *oitBuffers = nullptr; - int maxWidth = 0; - int maxHeight = 0; bool needAttachmentTransition = false; - int imageIndex = 0; - int renderPass = 0; + bool needDepthTransition = false; OITDescriptorSets descriptorSets; - std::vector> mainBuffers; + vk::Buffer curMainBuffer; bool dithering = false; }; @@ -175,8 +147,6 @@ public: void Term() { screenPipelineManager.reset(); - framebuffers.clear(); - finalColorAttachments.clear(); OITDrawer::Term(); } @@ -184,10 +154,11 @@ public: void EndFrame() override { - currentCommandBuffer.endRenderPass(); - if (config::EmulateFramebuffer) - { - scaleAndWriteFramebuffer(currentCommandBuffer, finalColorAttachments[GetCurrentImage()].get()); + if (!frameStarted) + return; + frameStarted = false; + if (config::EmulateFramebuffer) { + scaleAndWriteFramebuffer(currentCommandBuffer, colorAttachments[framebufferIndex].get()); } else { @@ -196,17 +167,17 @@ public: aspectRatio = getOutputFramebufferAspectRatio(); } currentCommandBuffer = nullptr; - OITDrawer::EndFrame(); frameRendered = true; } bool PresentFrame() { + EndFrame(); if (!frameRendered) return false; frameRendered = false; - GetContext()->PresentFrame(finalColorAttachments[GetCurrentImage()]->GetImage(), - finalColorAttachments[GetCurrentImage()]->GetImageView(), viewport.extent, aspectRatio); + GetContext()->PresentFrame(colorAttachments[framebufferIndex]->GetImage(), + colorAttachments[framebufferIndex]->GetImageView(), viewport.extent, aspectRatio); return true; } @@ -214,17 +185,17 @@ public: vk::CommandBuffer GetCurrentCommandBuffer() const { return currentCommandBuffer; } protected: - vk::Framebuffer GetFinalFramebuffer() const override { return *framebuffers[GetCurrentImage()]; } + vk::Framebuffer getFramebuffer(int renderPass, int renderPassCount) override; + int getFramebufferIndex() override { return framebufferIndex; } private: void MakeFramebuffers(const vk::Extent2D& viewport); - std::vector> finalColorAttachments; - std::vector framebuffers; std::unique_ptr screenPipelineManager; - std::vector transitionNeeded; bool frameRendered = false; float aspectRatio = 0.f; + bool frameStarted = false; + int framebufferIndex = 0; }; class OITTextureDrawer : public OITDrawer @@ -242,8 +213,8 @@ public: } void Term() { + framebuffer.reset(); colorAttachment.reset(); - framebuffers.clear(); rttPipelineManager.reset(); OITDrawer::Term(); } @@ -252,7 +223,7 @@ public: protected: vk::CommandBuffer NewFrame() override; - vk::Framebuffer GetFinalFramebuffer() const override { return *framebuffers[GetCurrentImage()]; } + vk::Framebuffer getFramebuffer(int renderPass, int renderPassCount) override; private: u32 textureAddr = 0; @@ -260,8 +231,8 @@ private: Texture *texture = nullptr; vk::Image colorImage; std::unique_ptr colorAttachment; - std::vector framebuffers; std::unique_ptr rttPipelineManager; + vk::UniqueFramebuffer framebuffer; TextureCache *textureCache = nullptr; }; diff --git a/core/rend/vulkan/oit/oit_pipeline.h b/core/rend/vulkan/oit/oit_pipeline.h index 39ae192f2..da32397bb 100644 --- a/core/rend/vulkan/oit/oit_pipeline.h +++ b/core/rend/vulkan/oit/oit_pipeline.h @@ -95,8 +95,8 @@ public: u32 polyParamsOffset, u32 polyParamsSize, vk::ImageView stencilImageView, vk::ImageView depthImageView, vk::ImageView paletteImageView, OITBuffers *oitBuffers) { - if (!perFrameDescSet) - perFrameDescSet = perFrameAlloc.alloc(); + perFrameDescSet = perFrameAlloc.alloc(); + perPolyDescSets.clear(); std::vector bufferInfos; bufferInfos.emplace_back(buffer, vertexUniformOffset, sizeof(VertexShaderUniforms)); @@ -144,8 +144,7 @@ public: void updateColorInputDescSet(int index, vk::ImageView colorImageView) { - if (!colorInputDescSets[index]) - colorInputDescSets[index] = colorInputAlloc.alloc(); + colorInputDescSets[index] = colorInputAlloc.alloc(); vk::DescriptorImageInfo colorImageInfo(vk::Sampler(), colorImageView, vk::ImageLayout::eShaderReadOnlyOptimal); vk::WriteDescriptorSet writeDescriptorSet(colorInputDescSets[index], 0, 0, vk::DescriptorType::eInputAttachment, colorImageInfo); diff --git a/core/rend/vulkan/oit/oit_renderer.cpp b/core/rend/vulkan/oit/oit_renderer.cpp index d85547aea..a8d3d37c0 100644 --- a/core/rend/vulkan/oit/oit_renderer.cpp +++ b/core/rend/vulkan/oit/oit_renderer.cpp @@ -53,6 +53,7 @@ public: { DEBUG_LOG(RENDERER, "OITVulkanRenderer::Term"); GetContext()->WaitIdle(); + texCommandPool.Term(); screenDrawer.Term(); textureDrawer.Term(); oitBuffers.Term(); @@ -61,6 +62,13 @@ public: BaseVulkanRenderer::Term(); } + void Process(TA_context* ctx) override + { + if (ctx->rend.isRTT) + screenDrawer.EndFrame(); + BaseVulkanRenderer::Process(ctx); + } + bool Render() override { try { @@ -81,7 +89,8 @@ public: } drawer->Draw(fogTexture.get(), paletteTexture.get()); - drawer->EndFrame(); + if (config::EmulateFramebuffer || pvrrc.isRTT) + drawer->EndFrame(); return !pvrrc.isRTT; } catch (const vk::SystemError& e) { diff --git a/core/rend/vulkan/oit/oit_renderpass.cpp b/core/rend/vulkan/oit/oit_renderpass.cpp index b19f6fc8c..db198206a 100644 --- a/core/rend/vulkan/oit/oit_renderpass.cpp +++ b/core/rend/vulkan/oit/oit_renderpass.cpp @@ -22,15 +22,16 @@ vk::UniqueRenderPass RenderPasses::MakeRenderPass(bool initial, bool last, bool loadClear) { + vk::AttachmentDescription attach0 = GetAttachment0Description(initial, last, loadClear); std::array attachmentDescriptions = { // Swap chain image - GetAttachment0Description(initial, last, loadClear), + attach0, // OP+PT color attachment vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), vk::Format::eR8G8B8A8Unorm, vk::SampleCountFlagBits::e1, initial ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, last ? vk::AttachmentStoreOp::eDontCare : vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, - initial ? vk::ImageLayout::eUndefined : vk::ImageLayout::eShaderReadOnlyOptimal, vk::ImageLayout::eShaderReadOnlyOptimal), + initial ? vk::ImageLayout::eUndefined : attach0.initialLayout, attach0.finalLayout), // OP+PT depth attachment vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), GetContext()->GetDepthFormat(), vk::SampleCountFlagBits::e1, initial ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, diff --git a/core/rend/vulkan/oit/oit_renderpass.h b/core/rend/vulkan/oit/oit_renderpass.h index fb400e838..e51ee797b 100644 --- a/core/rend/vulkan/oit/oit_renderpass.h +++ b/core/rend/vulkan/oit/oit_renderpass.h @@ -46,16 +46,20 @@ protected: return vk::AttachmentDescription(vk::AttachmentDescriptionFlags(), vk::Format::eR8G8B8A8Unorm, vk::SampleCountFlagBits::e1, loadClear ? vk::AttachmentLoadOp::eClear : vk::AttachmentLoadOp::eLoad, vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare, vk::AttachmentStoreOp::eDontCare, - config::EmulateFramebuffer && last ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal, + config::EmulateFramebuffer && initial ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal, config::EmulateFramebuffer && last ? vk::ImageLayout::eTransferSrcOptimal : vk::ImageLayout::eShaderReadOnlyOptimal); } virtual std::vector GetSubpassDependencies() const { - std::vector deps; - deps.emplace_back(2, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead, vk::DependencyFlagBits::eByRegion); - return deps; + if (config::EmulateFramebuffer) + return { { 2, VK_SUBPASS_EXTERNAL, + vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eHost, + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eHostRead, vk::DependencyFlagBits::eByRegion } }; + else + return { { 2, VK_SUBPASS_EXTERNAL, + vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead, vk::DependencyFlagBits::eByRegion } }; } private: @@ -76,14 +80,12 @@ protected: std::vector GetSubpassDependencies() const override { - std::vector deps; if (config::RenderToTextureBuffer) - deps.emplace_back(2, VK_SUBPASS_EXTERNAL, + return { { 2, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eTransfer | vk::PipelineStageFlagBits::eHost, - vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eHostRead); + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eHostRead } }; else - deps.emplace_back(2, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, - vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead); - return deps; + return { { 2, VK_SUBPASS_EXTERNAL, vk::PipelineStageFlagBits::eColorAttachmentOutput, vk::PipelineStageFlagBits::eFragmentShader, + vk::AccessFlagBits::eColorAttachmentWrite, vk::AccessFlagBits::eShaderRead } }; } }; diff --git a/core/rend/vulkan/pipeline.h b/core/rend/vulkan/pipeline.h index 72e32c74d..698984236 100644 --- a/core/rend/vulkan/pipeline.h +++ b/core/rend/vulkan/pipeline.h @@ -41,8 +41,8 @@ public: } void updateUniforms(vk::Buffer buffer, u32 vertexUniformOffset, u32 fragmentUniformOffset, vk::ImageView fogImageView, vk::ImageView paletteImageView) { - if (!perFrameDescSet) - perFrameDescSet = perFrameAlloc.alloc(); + perFrameDescSet = perFrameAlloc.alloc(); + perPolyDescSets.clear(); std::vector bufferInfos; bufferInfos.emplace_back(buffer, vertexUniformOffset, sizeof(VertexShaderUniforms)); diff --git a/core/rend/vulkan/vk_context_lr.cpp b/core/rend/vulkan/vk_context_lr.cpp index 9051d1891..4f999de65 100644 --- a/core/rend/vulkan/vk_context_lr.cpp +++ b/core/rend/vulkan/vk_context_lr.cpp @@ -373,7 +373,7 @@ void VulkanContext::beginFrame(vk::Extent2D extent) } commandPool.BeginFrame(); const std::array clear_colors = { getBorderColor(), vk::ClearDepthStencilValue{ 0.f, 0 } }; - cmdBuffer = commandPool.Allocate(); + cmdBuffer = commandPool.Allocate(true); cmdBuffer.begin(vk::CommandBufferBeginInfo(vk::CommandBufferUsageFlagBits::eOneTimeSubmit)); overlay->Prepare(cmdBuffer, true, true); diff --git a/core/rend/vulkan/vulkan.h b/core/rend/vulkan/vulkan.h index 63c8b24e5..825e7bc65 100644 --- a/core/rend/vulkan/vulkan.h +++ b/core/rend/vulkan/vulkan.h @@ -51,10 +51,14 @@ template class Deleter : public Deletable { public: - Deleter(T *p) : p(p) {} + Deleter() = delete; + explicit Deleter(T& o) : o(o) {} + Deleter(T&& o) : o(std::move(o)) {} ~Deleter() override { - delete p; + if constexpr (std::is_pointer_v) + delete o; } + private: - T *p; + T o; }; diff --git a/core/rend/vulkan/vulkan_renderer.cpp b/core/rend/vulkan/vulkan_renderer.cpp index d32a70e0d..56f053ad0 100644 --- a/core/rend/vulkan/vulkan_renderer.cpp +++ b/core/rend/vulkan/vulkan_renderer.cpp @@ -115,6 +115,7 @@ BaseTextureCacheData *BaseVulkanRenderer::GetTexture(TSP tsp, TCW tcw) void BaseVulkanRenderer::Process(TA_context* ctx) { + framebufferRendered = false; if (KillTex) textureCache.Clear(); @@ -127,6 +128,7 @@ void BaseVulkanRenderer::Process(TA_context* ctx) ta_parse(ctx, true); + // TODO can't update fog or palette twice in multi render CheckFogTexture(); CheckPaletteTexture(); texCommandBuffer.end(); @@ -293,7 +295,6 @@ bool BaseVulkanRenderer::presentFramebuffer() return false; GetContext()->PresentFrame(fbTexture->GetImage(), fbTexture->GetImageView(), fbTexture->getSize(), getDCFramebufferAspectRatio()); - framebufferRendered = false; return true; } @@ -319,12 +320,20 @@ public: { DEBUG_LOG(RENDERER, "VulkanRenderer::Term"); GetContext()->WaitIdle(); + texCommandPool.Term(); // make sure all in-flight buffers are returned screenDrawer.Term(); textureDrawer.Term(); samplerManager.term(); BaseVulkanRenderer::Term(); } + void Process(TA_context* ctx) override + { + if (ctx->rend.isRTT) + screenDrawer.EndRenderPass(); + BaseVulkanRenderer::Process(ctx); + } + bool Render() override { try { @@ -345,7 +354,9 @@ public: } drawer->Draw(fogTexture.get(), paletteTexture.get()); - drawer->EndRenderPass(); + if (config::EmulateFramebuffer || pvrrc.isRTT) + // delay ending the render pass in case of multi render + drawer->EndRenderPass(); return !pvrrc.isRTT; } catch (const vk::SystemError& e) { From e2e9a54e0e5f977b6867ab5ebd9bf17b2bdf4e59 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 15 May 2024 14:20:13 +0200 Subject: [PATCH 77/86] std::swap is undefined for vector --- core/rend/vulkan/commandpool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rend/vulkan/commandpool.cpp b/core/rend/vulkan/commandpool.cpp index 587bb1cfc..5e34110d5 100644 --- a/core/rend/vulkan/commandpool.cpp +++ b/core/rend/vulkan/commandpool.cpp @@ -90,7 +90,7 @@ void CommandPool::EndFrame() { for (size_t i = 0; i < len; i++) if (lastBuffers[i] && !lastBuffers[i + 1]) { - std::swap(lastBuffers[i], lastBuffers[i + 1]); + std::vector::swap(lastBuffers[i], lastBuffers[i + 1]); std::swap(commandBuffers[i], commandBuffers[i + 1]); } len--; From 4f834610b36ca65f86177ad88711867921397351 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Wed, 15 May 2024 19:31:25 +0200 Subject: [PATCH 78/86] gl: refactor vmu and xhair drawing. Blit rendered frame only once Refactor vmu and xhair drawing to use the same code as libretro. Fix an issue were each rendered frame was blit twice to the backbuffer. --- .../imgui/backends/imgui_impl_opengl3.cpp | 16 -- core/rend/gl4/gles.cpp | 20 +- core/rend/gles/gldraw.cpp | 224 ++++++++++-------- core/rend/gles/gles.cpp | 18 +- core/rend/gles/gles.h | 6 +- core/rend/gles/opengl_driver.cpp | 137 +---------- core/rend/gles/opengl_driver.h | 31 +-- core/rend/gles/quad.cpp | 15 +- core/ui/gui.cpp | 3 - core/ui/imgui_driver.h | 3 - 10 files changed, 171 insertions(+), 302 deletions(-) diff --git a/core/deps/imgui/backends/imgui_impl_opengl3.cpp b/core/deps/imgui/backends/imgui_impl_opengl3.cpp index 6f485a0a6..6eba3a348 100644 --- a/core/deps/imgui/backends/imgui_impl_opengl3.cpp +++ b/core/deps/imgui/backends/imgui_impl_opengl3.cpp @@ -66,9 +66,7 @@ #include "TargetConditionals.h" #endif -#include "wsi/gl_context.h" #include "rend/gles/glcache.h" -#include "hw/pvr/Renderer_if.h" // OpenGL Data static char g_GlslVersionString[32] = ""; @@ -81,7 +79,6 @@ static unsigned int g_VboHandle = 0, g_ElementsHandle = 0; // Functions static bool ImGui_ImplOpenGL3_CreateDeviceObjects(); static void ImGui_ImplOpenGL3_DestroyDeviceObjects(); -static void ImGui_ImplOpenGL3_DrawBackground(); bool ImGui_ImplOpenGL3_Init() { @@ -114,7 +111,6 @@ void ImGui_ImplOpenGL3_NewFrame() { if (!g_FontTexture) ImGui_ImplOpenGL3_CreateDeviceObjects(); - ImGui_ImplOpenGL3_DrawBackground(); } // OpenGL3 Render function. @@ -489,15 +485,3 @@ static void ImGui_ImplOpenGL3_DestroyDeviceObjects() ImGui_ImplOpenGL3_DestroyFontsTexture(); } - -static void ImGui_ImplOpenGL3_DrawBackground() -{ -#ifndef TARGET_IPHONE - glBindFramebuffer(GL_FRAMEBUFFER, 0); -#endif - glcache.Disable(GL_SCISSOR_TEST); - glcache.ClearColor(0, 0, 0, 0); - glClear(GL_COLOR_BUFFER_BIT); - if (renderer != nullptr) - renderer->RenderLastFrame(); -} diff --git a/core/rend/gl4/gles.cpp b/core/rend/gl4/gles.cpp index e09987c7c..ef65cf9c8 100644 --- a/core/rend/gl4/gles.cpp +++ b/core/rend/gl4/gles.cpp @@ -711,8 +711,8 @@ struct OpenGL4Renderer : OpenGLRenderer if (!config::EmulateFramebuffer) { - DrawOSD(false); frameRendered = true; + DrawOSD(false); renderVideoRouting(); } restoreCurrentFramebuffer(); @@ -729,23 +729,13 @@ struct OpenGL4Renderer : OpenGLRenderer bool renderFrame(int width, int height); -#ifdef LIBRETRO void DrawOSD(bool clearScreen) override { - void DrawVmuTexture(u8 vmu_screen_number, int width, int height); - void DrawGunCrosshair(u8 port, int width, int height); - - if (settings.platform.isConsole()) - { - for (int vmu_screen_number = 0 ; vmu_screen_number < 4 ; vmu_screen_number++) - if (vmu_lcd_status[vmu_screen_number * 2]) - DrawVmuTexture(vmu_screen_number, width, height); - } - - for (int lightgun_port = 0 ; lightgun_port < 4 ; lightgun_port++) - DrawGunCrosshair(lightgun_port, width, height); - } + drawVmusAndCrosshairs(width, height); +#ifndef LIBRETRO + gui_display_osd(); #endif + } }; //setup diff --git a/core/rend/gles/gldraw.cpp b/core/rend/gles/gldraw.cpp index 85fbb874b..23840c0e2 100644 --- a/core/rend/gles/gldraw.cpp +++ b/core/rend/gles/gldraw.cpp @@ -6,6 +6,7 @@ #include "rend/transform_matrix.h" #ifdef LIBRETRO #include "postprocess.h" +#include "vmu_xhair.h" #endif #include @@ -888,57 +889,94 @@ bool OpenGLRenderer::GetLastFrame(std::vector& data, int& width, int& height return true; } -#ifdef LIBRETRO -#include "vmu_xhair.h" +static GLuint vmuTextureId[8] {}; +static GLuint lightgunTextureId {}; +static u64 vmuLastUpdated[8] {}; -static GLuint vmuTextureId[4] {}; -static GLuint lightgunTextureId[4] {}; -static u64 vmuLastUpdated[4] {}; - -static void updateVmuTexture(int vmu_screen_number) +static void updateVmuTexture(int vmuIndex) { - if (vmuTextureId[vmu_screen_number] == 0) + if (vmuTextureId[vmuIndex] == 0) { - vmuTextureId[vmu_screen_number] = glcache.GenTexture(); - glcache.BindTexture(GL_TEXTURE_2D, vmuTextureId[vmu_screen_number]); + vmuTextureId[vmuIndex] = glcache.GenTexture(); + glcache.BindTexture(GL_TEXTURE_2D, vmuTextureId[vmuIndex]); glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); } else - glcache.BindTexture(GL_TEXTURE_2D, vmuTextureId[vmu_screen_number]); + glcache.BindTexture(GL_TEXTURE_2D, vmuTextureId[vmuIndex]); + const u32 *data = vmu_lcd_data[vmuIndex]; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 48, 32, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - const u32 *data = vmu_lcd_data[vmu_screen_number * 2]; - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, VMU_SCREEN_WIDTH, VMU_SCREEN_HEIGHT, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); - - vmuLastUpdated[vmu_screen_number * 2] = vmuLastChanged[vmu_screen_number * 2]; + vmuLastUpdated[vmuIndex] = vmuLastChanged[vmuIndex]; } -void DrawVmuTexture(u8 vmu_screen_number, int width, int height) +static void drawVmuTexture(u8 vmuIndex, int width, int height) { - float x = 8.f * width / 640.f; - float y = 8.f * height / 480.f; - float w = (float)VMU_SCREEN_WIDTH * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult * 4.f / 3.f / gl.ofbo.aspectRatio * width / 640.f; - float h = (float)VMU_SCREEN_HEIGHT * vmu_screen_params[vmu_screen_number].vmu_screen_size_mult * height / 480.f; + const float *color = nullptr; +#ifndef LIBRETRO + const float vmu_padding = 8.f * settings.display.uiScale; + const float w = 96.f * settings.display.uiScale; + const float h = 64.f * settings.display.uiScale; - if (vmuLastChanged[vmu_screen_number * 2] != vmuLastUpdated[vmu_screen_number * 2] || vmuTextureId[vmu_screen_number] == 0) - updateVmuTexture(vmu_screen_number); - - switch (vmu_screen_params[vmu_screen_number].vmu_screen_position) + float x; + float y; + if (vmuIndex & 2) + x = width - vmu_padding - w; + else + x = vmu_padding; + if (vmuIndex & 4) { - case UPPER_LEFT: - break; - case UPPER_RIGHT: - x = width - x - w; - break; - case LOWER_LEFT: - y = height - y - h; - break; - case LOWER_RIGHT: - x = width - x - w; - y = height - y - h; - break; + y = height - vmu_padding - h; + if (vmuIndex & 1) + y -= vmu_padding + h; } + else + { + y = vmu_padding; + if (vmuIndex & 1) + y += vmu_padding + h; + } + const float blend_factor[4] = { 0.75f, 0.75f, 0.75f, 0.75f }; + color = blend_factor; +#else + if (vmuIndex & 1) + return; + const float vmu_padding_x = 8.f * width / 640.f * 4.f / 3.f / gl.ofbo.aspectRatio; + const float vmu_padding_y = 8.f * height / 480.f; + const float w = (float)VMU_SCREEN_WIDTH * width / 640.f * 4.f / 3.f / gl.ofbo.aspectRatio + * vmu_screen_params[vmuIndex / 2].vmu_screen_size_mult; + const float h = (float)VMU_SCREEN_HEIGHT * height / 480.f + * vmu_screen_params[vmuIndex / 2].vmu_screen_size_mult; + + float x; + float y; + + switch (vmu_screen_params[vmuIndex / 2].vmu_screen_position) + { + case UPPER_LEFT: + default: + x = vmu_padding_x; + y = vmu_padding_y; + break; + case UPPER_RIGHT: + x = width - vmu_padding_x - w; + y = vmu_padding_y; + break; + case LOWER_LEFT: + x = vmu_padding_x; + y = height - vmu_padding_y - h; + break; + case LOWER_RIGHT: + x = width - vmu_padding_x - w; + y = height - vmu_padding_y - h; + break; + } +#endif + + if (vmuLastChanged[vmuIndex] != vmuLastUpdated[vmuIndex] || vmuTextureId[vmuIndex] == 0) + updateVmuTexture(vmuIndex); + float x1 = (x + w) * 2 / width - 1; float y1 = -(y + h) * 2 / height + 1; x = x * 2 / width - 1; @@ -951,90 +989,90 @@ void DrawVmuTexture(u8 vmu_screen_number, int width, int height) }; glcache.Enable(GL_BLEND); glcache.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - drawQuad(vmuTextureId[vmu_screen_number], false, false, vertices); + drawQuad(vmuTextureId[vmuIndex], false, false, vertices, color); } -static void updateLightGunTexture(int port) +static void updateLightGunTexture() { - s32 x,y ; - u8 temp_tex_buffer[LIGHTGUN_CROSSHAIR_SIZE*LIGHTGUN_CROSSHAIR_SIZE*4]; - u8 *dst = temp_tex_buffer; - u8 *src = NULL ; - - if (lightgunTextureId[port] == 0) + if (lightgunTextureId == 0) { - lightgunTextureId[port] = glcache.GenTexture(); - glcache.BindTexture(GL_TEXTURE_2D, lightgunTextureId[port]); + lightgunTextureId = glcache.GenTexture(); + glcache.BindTexture(GL_TEXTURE_2D, lightgunTextureId); glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glcache.TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, getCrosshairTextureData()); } - else - glcache.BindTexture(GL_TEXTURE_2D, lightgunTextureId[port]); - - u8* colour = &( lightgun_palette[ lightgun_params[port].colour * 3 ] ); - - for ( y = LIGHTGUN_CROSSHAIR_SIZE-1 ; y >= 0 ; y--) - { - src = lightgun_img_crosshair + (y*LIGHTGUN_CROSSHAIR_SIZE) ; - - for ( x = 0 ; x < LIGHTGUN_CROSSHAIR_SIZE ; x++) - { - if ( src[x] ) - { - *dst++ = colour[0] ; - *dst++ = colour[1] ; - *dst++ = colour[2] ; - *dst++ = 0xFF ; - } - else - { - *dst++ = 0 ; - *dst++ = 0 ; - *dst++ = 0 ; - *dst++ = 0 ; - } - } - } - - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, LIGHTGUN_CROSSHAIR_SIZE, LIGHTGUN_CROSSHAIR_SIZE, 0, GL_RGBA, GL_UNSIGNED_BYTE, temp_tex_buffer); - - lightgun_params[port].dirty = false; } -void DrawGunCrosshair(u8 port, int width, int height) +static void drawGunCrosshair(u8 port, int width, int height) { - if (lightgun_params[port].offscreen || lightgun_params[port].colour == 0) + if (config::CrosshairColor[port] == 0) + return; + if (settings.platform.isConsole() + && config::MapleMainDevices[port] != MDT_LightGun) return; - float w = lightgun_crosshair_size * 4.f / 3.f / gl.ofbo.aspectRatio * config::RenderResolution / 480.f; - float h = lightgun_crosshair_size * config::RenderResolution / 480.f; auto [x, y] = getCrosshairPosition(port); - x -= w / 2; - y -= h / 2; +#ifdef LIBRETRO + float halfWidth = lightgun_crosshair_size / 2.f / config::ScreenStretching * 100.f * config::RenderResolution / 480.f; + float halfHeight = lightgun_crosshair_size / 2.f * config::RenderResolution / 480.f; + x /= config::ScreenStretching / 100.f; +#else + float halfWidth = config::CrosshairSize * settings.display.uiScale / 2.f; + float halfHeight = halfWidth; +#endif - if (lightgun_params[port].dirty || lightgunTextureId[port] == 0) - updateLightGunTexture(port); + updateLightGunTexture(); - float x1 = (x + w) * 2 / width - 1; - float y1 = -(y + h) * 2 / height + 1; - x = x * 2 / width - 1; - y = -y * 2 / height + 1; + float x1 = (x + halfWidth) * 2 / width - 1; + float y1 = -(y + halfHeight) * 2 / height + 1; + x = (x - halfWidth) * 2 / width - 1; + y = -(y - halfHeight) * 2 / height + 1; float vertices[20] = { x, y1, 1.f, 0.f, 0.f, x, y, 1.f, 0.f, 1.f, x1, y1, 1.f, 1.f, 0.f, x1, y, 1.f, 1.f, 1.f, }; + const float color[4] = { + (config::CrosshairColor[port] & 0xff) / 255.f, + ((config::CrosshairColor[port] >> 8) & 0xff) / 255.f, + ((config::CrosshairColor[port] >> 16) & 0xff) / 255.f, + ((config::CrosshairColor[port] >> 24) & 0xff) / 255.f + }; glcache.Enable(GL_BLEND); glcache.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - drawQuad(lightgunTextureId[port], false, false, vertices); + drawQuad(lightgunTextureId, false, false, vertices, color); +} + +void drawVmusAndCrosshairs(int width, int height) +{ +#ifndef LIBRETRO + width = settings.display.width; + height = settings.display.height; + glViewport(0, 0, width, height); + glBindFramebuffer(GL_FRAMEBUFFER, gl.ofbo.origFbo); + const bool showVmus = config::FloatVMUs; +#else + const bool showVmus = true; +#endif + + if (settings.platform.isConsole() && showVmus) + { + for (int i = 0; i < 8 ; i++) + if (vmu_lcd_status[i]) + drawVmuTexture(i, width, height); + } + + for (int i = 0 ; i < 4 ; i++) + drawGunCrosshair(i, width, height); + glCheck(); } void termVmuLightgun() { glcache.DeleteTextures(std::size(vmuTextureId), vmuTextureId); memset(vmuTextureId, 0, sizeof(vmuTextureId)); - glcache.DeleteTextures(std::size(lightgunTextureId), lightgunTextureId); - memset(lightgunTextureId, 0, sizeof(lightgunTextureId)); + glcache.DeleteTextures(1, &lightgunTextureId); + lightgunTextureId = 0; } -#endif diff --git a/core/rend/gles/gles.cpp b/core/rend/gles/gles.cpp index aa88bf258..e547c1689 100644 --- a/core/rend/gles/gles.cpp +++ b/core/rend/gles/gles.cpp @@ -516,9 +516,9 @@ void termGLCommon() gl.ofbo2.framebuffer.reset(); gl.fbscaling.framebuffer.reset(); gl.videorouting.framebuffer.reset(); + termVmuLightgun(); #ifdef LIBRETRO postProcessor.term(); - termVmuLightgun(); #endif } @@ -1122,21 +1122,9 @@ static void updatePaletteTexture(GLenum texture_slot) void OpenGLRenderer::DrawOSD(bool clear_screen) { -#ifdef LIBRETRO - void DrawVmuTexture(u8 vmu_screen_number, int width, int height); - void DrawGunCrosshair(u8 port, int width, int height); + drawVmusAndCrosshairs(width, height); - if (settings.platform.isConsole()) - { - for (int vmu_screen_number = 0 ; vmu_screen_number < 4 ; vmu_screen_number++) - if (vmu_lcd_status[vmu_screen_number * 2]) - DrawVmuTexture(vmu_screen_number, width, height); - } - - for (int lightgun_port = 0 ; lightgun_port < 4 ; lightgun_port++) - DrawGunCrosshair(lightgun_port, width, height); - -#else +#ifndef LIBRETRO gui_display_osd(); #ifdef __ANDROID__ if (gl.OSD_SHADER.osd_tex == 0) diff --git a/core/rend/gles/gles.h b/core/rend/gles/gles.h index f7ebc131e..ce1bd6423 100755 --- a/core/rend/gles/gles.h +++ b/core/rend/gles/gles.h @@ -571,7 +571,7 @@ protected: void initQuad(); void termQuad(); -void drawQuad(GLuint texId, bool rotate = false, bool swapY = false, const float *coords = nullptr); +void drawQuad(GLuint texId, bool rotate = false, bool swapY = false, const float *coords = nullptr, const float *color = nullptr); extern const char* ShaderCompatSource; extern const char *VertexCompatShader; @@ -586,7 +586,9 @@ public: } }; +void drawVmusAndCrosshairs(int width, int height); +void termVmuLightgun(); + #ifdef LIBRETRO extern "C" struct retro_hw_render_callback hw_render; -void termVmuLightgun(); #endif diff --git a/core/rend/gles/opengl_driver.cpp b/core/rend/gles/opengl_driver.cpp index 425dfd778..001999cc1 100644 --- a/core/rend/gles/opengl_driver.cpp +++ b/core/rend/gles/opengl_driver.cpp @@ -19,11 +19,9 @@ #include "opengl_driver.h" #include "imgui_impl_opengl3.h" #include "wsi/gl_context.h" -#include "rend/osd.h" -#include "ui/gui.h" -#include "ui/gui_util.h" #include "glcache.h" #include "gles.h" +#include "hw/pvr/Renderer_if.h" #ifndef GL_CLAMP_TO_BORDER #define GL_CLAMP_TO_BORDER 0x812D @@ -32,36 +30,15 @@ #define GL_TEXTURE_BORDER_COLOR 0x1004 #endif -static constexpr int vmu_coords[8][2] = { - { 0 , 0 }, - { 0 , 0 }, - { 1 , 0 }, - { 1 , 0 }, - { 0 , 1 }, - { 0 , 1 }, - { 1 , 1 }, - { 1 , 1 }, -}; -constexpr int VMU_WIDTH = 96; -constexpr int VMU_HEIGHT = 64; -constexpr int VMU_PADDING = 8; - OpenGLDriver::OpenGLDriver() { ImGui_ImplOpenGL3_Init(); - EventManager::listen(Event::Resume, emuEventCallback, this); - EventManager::listen(Event::Terminate, emuEventCallback, this); } OpenGLDriver::~OpenGLDriver() { - EventManager::unlisten(Event::Resume, emuEventCallback, this); - EventManager::unlisten(Event::Terminate, emuEventCallback, this); - std::vector texIds; - texIds.reserve(1 + textures.size()); - if (crosshairTexId != ImTextureID()) - texIds.push_back((GLuint)(uintptr_t)crosshairTexId); + texIds.reserve(textures.size()); for (const auto& it : textures) texIds.push_back((GLuint)(uintptr_t)it.second); if (!texIds.empty()) @@ -69,105 +46,6 @@ OpenGLDriver::~OpenGLDriver() ImGui_ImplOpenGL3_Shutdown(); } -void OpenGLDriver::reset() -{ - ImGuiDriver::reset(); - for (auto& tex : vmu_lcd_tex_ids) - tex = ImTextureID{}; - vmuLastChanged.fill({}); -} - -void OpenGLDriver::updateVmuTextures() -{ - for (int i = 0; i < 8; i++) - { - if (!vmu_lcd_status[i]) - continue; - - if (this->vmuLastChanged[i] != ::vmuLastChanged[i] || vmu_lcd_tex_ids[i] == ImTextureID()) - { - try { - vmu_lcd_tex_ids[i] = updateTexture(":vmugl:" + std::to_string(i), (const u8 *)vmu_lcd_data[i], 48, 32, true); - } catch (...) { - continue; - } - if (vmu_lcd_tex_ids[i] != ImTextureID()) - this->vmuLastChanged[i] = ::vmuLastChanged[i]; - } - } -} - -void OpenGLDriver::displayVmus() -{ - if (!gameStarted) - return; - updateVmuTextures(); - const ScaledVec2 size(VMU_WIDTH, VMU_HEIGHT); - const float padding = uiScaled(VMU_PADDING); - ImDrawList *dl = ImGui::GetForegroundDrawList(); - for (int i = 0; i < 8; i++) - { - if (!vmu_lcd_status[i]) - continue; - - int x = vmu_coords[i][0]; - int y = vmu_coords[i][1]; - ImVec2 pos; - if (x == 0) - pos.x = padding; - else - pos.x = ImGui::GetIO().DisplaySize.x - size.x - padding; - if (y == 0) - { - pos.y = padding; - if (i & 1) - pos.y += size.y + padding; - } - else - { - pos.y = ImGui::GetIO().DisplaySize.y - size.y - padding; - if (i & 1) - pos.y -= size.y + padding; - } - ImVec2 pos_b = pos + size; - dl->AddImage(vmu_lcd_tex_ids[i], pos, pos_b, ImVec2(0, 1), ImVec2(1, 0), 0xC0ffffff); - } -} - -void OpenGLDriver::displayCrosshairs() -{ - if (!gameStarted) - return; - if (!crosshairsNeeded()) - return; - - if (crosshairTexId == ImTextureID()) - crosshairTexId = updateTexture("__crosshair", (const u8 *)getCrosshairTextureData(), 16, 16, true); - - ImGui::SetNextWindowBgAlpha(0); - ImGui::SetNextWindowPos(ImVec2(0, 0)); - ImGui::SetNextWindowSize(ImGui::GetIO().DisplaySize); - - ImGui::Begin("xhair-window", nullptr, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoInputs - | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoFocusOnAppearing); - for (u32 i = 0; i < config::CrosshairColor.size(); i++) - { - if (config::CrosshairColor[i] == 0) - continue; - if (settings.platform.isConsole() && config::MapleMainDevices[i] != MDT_LightGun) - continue; - - ImVec2 pos; - std::tie(pos.x, pos.y) = getCrosshairPosition(i); - pos.x -= (config::CrosshairSize * settings.display.uiScale) / 2.f; - pos.y += (config::CrosshairSize * settings.display.uiScale) / 2.f; - ImVec2 pos_b(pos.x + config::CrosshairSize * settings.display.uiScale, pos.y - config::CrosshairSize * settings.display.uiScale); - - ImGui::GetWindowDrawList()->AddImage(crosshairTexId, pos, pos_b, ImVec2(0, 1), ImVec2(1, 0), config::CrosshairColor[i]); - } - ImGui::End(); -} - void OpenGLDriver::newFrame() { ImGui_ImplOpenGL3_NewFrame(); @@ -175,6 +53,17 @@ void OpenGLDriver::newFrame() void OpenGLDriver::renderDrawData(ImDrawData* drawData, bool gui_open) { + if (gui_open) + { +#ifndef TARGET_IPHONE + glBindFramebuffer(GL_FRAMEBUFFER, 0); +#endif + glcache.Disable(GL_SCISSOR_TEST); + glcache.ClearColor(0, 0, 0, 0); + glClear(GL_COLOR_BUFFER_BIT); + if (renderer != nullptr) + renderer->RenderLastFrame(); + } ImGui_ImplOpenGL3_RenderDrawData(drawData); if (gui_open) frameRendered = true; diff --git a/core/rend/gles/opengl_driver.h b/core/rend/gles/opengl_driver.h index d9262d541..77c63b9f7 100644 --- a/core/rend/gles/opengl_driver.h +++ b/core/rend/gles/opengl_driver.h @@ -18,7 +18,6 @@ */ #pragma once #include "ui/imgui_driver.h" -#include "emulator.h" #include class OpenGLDriver final : public ImGuiDriver @@ -27,19 +26,16 @@ public: OpenGLDriver(); ~OpenGLDriver() override; - void displayVmus() override; - void displayCrosshairs() override; - void newFrame() override; void renderDrawData(ImDrawData* drawData, bool gui_open) override; void present() override; - void reset() override; void setFrameRendered() override { frameRendered = true; } - ImTextureID getTexture(const std::string& name) override { + ImTextureID getTexture(const std::string& name) override + { auto it = textures.find(name); if (it != textures.end()) return it->second; @@ -51,29 +47,6 @@ public: void deleteTexture(const std::string& name) override; private: - void emuEvent(Event event) - { - switch (event) - { - case Event::Resume: - gameStarted = true; - break; - case Event::Terminate: - gameStarted = false; - break; - default: - break; - } - } - static void emuEventCallback(Event event, void *p) { - ((OpenGLDriver *)p)->emuEvent(event); - } - void updateVmuTextures(); - - ImTextureID crosshairTexId = ImTextureID(); - ImTextureID vmu_lcd_tex_ids[8] {}; - std::array vmuLastChanged {}; - bool gameStarted = false; bool frameRendered = false; std::unordered_map textures; }; diff --git a/core/rend/gles/quad.cpp b/core/rend/gles/quad.cpp index c14c8f794..8c6047e97 100644 --- a/core/rend/gles/quad.cpp +++ b/core/rend/gles/quad.cpp @@ -38,10 +38,11 @@ static const char* FragmentShader = R"( in mediump vec2 vtx_uv; uniform sampler2D tex; +uniform mediump vec4 tint; void main() { - gl_FragColor = texture(tex, vtx_uv); + gl_FragColor = tint * texture(tex, vtx_uv); } )"; @@ -63,7 +64,9 @@ protected: }; static GLuint shader; +static GLint tintUniform; static GLuint rot90shader; +static GLint rot90TintUniform; static QuadVertexArray quadVertexArray; static QuadVertexArray quadVertexArraySwapY; static std::unique_ptr quadBuffer; @@ -88,11 +91,13 @@ void initQuad() shader = gl_CompileAndLink(vertexShader.generate().c_str(), fragmentGlsl.c_str()); GLint tex = glGetUniformLocation(shader, "tex"); glUniform1i(tex, 0); // texture 0 + tintUniform = glGetUniformLocation(shader, "tint"); vertexShader.setConstant("ROTATE", 1); rot90shader = gl_CompileAndLink(vertexShader.generate().c_str(), fragmentGlsl.c_str()); tex = glGetUniformLocation(rot90shader, "tex"); glUniform1i(tex, 0); // texture 0 + rot90TintUniform = glGetUniformLocation(rot90shader, "tint"); } if (quadIndexBuffer == nullptr) { @@ -145,7 +150,7 @@ void termQuad() } // coords is an optional array of 20 floats (4 vertices with x,y,z,u,v each) -void drawQuad(GLuint texId, bool rotate, bool swapY, const float *coords) +void drawQuad(GLuint texId, bool rotate, bool swapY, const float *coords, const float *color) { glcache.Disable(GL_SCISSOR_TEST); glcache.Disable(GL_DEPTH_TEST); @@ -157,6 +162,12 @@ void drawQuad(GLuint texId, bool rotate, bool swapY, const float *coords) glActiveTexture(GL_TEXTURE0); glcache.BindTexture(GL_TEXTURE_2D, texId); + if (color == nullptr) { + static constexpr float white[4] { 1.f, 1.f, 1.f, 1.f }; + color = white; + } + glUniform4fv(rotate ? rot90TintUniform : tintUniform, 1, color); + if (coords == nullptr) { if (swapY) diff --git a/core/ui/gui.cpp b/core/ui/gui.cpp index 4ed506251..bcaf568ab 100644 --- a/core/ui/gui.cpp +++ b/core/ui/gui.cpp @@ -3538,9 +3538,6 @@ void gui_draw_osd() dl->AddText(largeFont, largeFont->FontSize, pos, col, &message.front(), &message.back() + 1, maxW); } } - imguiDriver->displayCrosshairs(); // OpenGL only - if (config::FloatVMUs) - imguiDriver->displayVmus(); // OpenGL only if (ggpo::active()) { diff --git a/core/ui/imgui_driver.h b/core/ui/imgui_driver.h index 68d20e599..bc60b3a25 100644 --- a/core/ui/imgui_driver.h +++ b/core/ui/imgui_driver.h @@ -38,9 +38,6 @@ public: virtual void newFrame() = 0; virtual void renderDrawData(ImDrawData* drawData, bool gui_open) = 0; - virtual void displayVmus() {} // TODO OpenGL only. Get rid of it - virtual void displayCrosshairs() {} // same - virtual void present() = 0; virtual void setFrameRendered() {} From ec82c7b9ed58000d21da4b2900c7f8998465c512 Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 18 May 2024 12:11:08 +0200 Subject: [PATCH 79/86] gl: only display crosshairs when needed regression due to 4f834610b36ca65f86177ad88711867921397351 --- core/rend/gles/gldraw.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/rend/gles/gldraw.cpp b/core/rend/gles/gldraw.cpp index 23840c0e2..b69c607e0 100644 --- a/core/rend/gles/gldraw.cpp +++ b/core/rend/gles/gldraw.cpp @@ -1064,8 +1064,10 @@ void drawVmusAndCrosshairs(int width, int height) drawVmuTexture(i, width, height); } - for (int i = 0 ; i < 4 ; i++) - drawGunCrosshair(i, width, height); + if (crosshairsNeeded()) { + for (int i = 0 ; i < 4 ; i++) + drawGunCrosshair(i, width, height); + } glCheck(); } From b7a2e605f907b2fef99a40f20781c9926169b9ad Mon Sep 17 00:00:00 2001 From: Flyinghead Date: Sat, 18 May 2024 12:36:29 +0200 Subject: [PATCH 80/86] dx11: use ALLOW_TEARING flag to disable vsync if available --- core/rend/dx11/dx11context.cpp | 30 ++++++++++++++++++------------ core/rend/dx11/dx11context.h | 1 + 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/core/rend/dx11/dx11context.cpp b/core/rend/dx11/dx11context.cpp index abc8a3e9b..0e0d5cf4d 100644 --- a/core/rend/dx11/dx11context.cpp +++ b/core/rend/dx11/dx11context.cpp @@ -64,6 +64,7 @@ bool DX11Context::init(bool keepCurrentWindow) ComPtr dxgiFactory6; ComPtr dxgiAdapter; HRESULT hr; + allowTearing = false; hr = CreateDXGIFactory1(__uuidof(IDXGIFactory1), (void **)&dxgiFactory.get()); if (SUCCEEDED(hr)) { @@ -71,6 +72,10 @@ bool DX11Context::init(bool keepCurrentWindow) if (dxgiFactory6) { dxgiFactory6->EnumAdapterByGpuPreference(0, DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE, __uuidof(IDXGIAdapter), (void **)&dxgiAdapter.get()); + UINT tearing; + if (SUCCEEDED(dxgiFactory6->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &tearing, + sizeof(tearing))) && tearing != 0) + allowTearing = true; dxgiFactory6.reset(); } } @@ -127,6 +132,8 @@ bool DX11Context::init(bool keepCurrentWindow) desc.BufferCount = 2; desc.SampleDesc.Count = 1; desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + if (allowTearing) + desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; #ifdef TARGET_UWP desc.Width = settings.display.width; @@ -157,6 +164,8 @@ bool DX11Context::init(bool keepCurrentWindow) desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; desc.SampleDesc.Count = 1; desc.SampleDesc.Quality = 0; + if (allowTearing) + desc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; hr = dxgiFactory->CreateSwapChain(pDevice, &desc, &swapchain.get()); } @@ -221,26 +230,23 @@ void DX11Context::Present() frameRendered = false; bool swapOnVSync = !settings.input.fastForwardMode && config::VSync; HRESULT hr; - if (!swapchain) - { + if (!swapchain) { hr = DXGI_ERROR_DEVICE_RESET; } - else if (swapOnVSync) - { + else if (swapOnVSync) { int swapInterval = std::min(4, std::max(1, (int)(settings.display.refreshRate / 60))); hr = swapchain->Present(swapInterval, 0); } - else - { - hr = swapchain->Present(0, DXGI_PRESENT_DO_NOT_WAIT); + else { + hr = swapchain->Present(0, allowTearing ? DXGI_PRESENT_ALLOW_TEARING : DXGI_PRESENT_DO_NOT_WAIT); } - if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) - { + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { WARN_LOG(RENDERER, "Present failed: device removed/reset"); handleDeviceLost(); } - else if (hr != DXGI_ERROR_WAS_STILL_DRAWING && FAILED(hr)) + else if (hr != DXGI_ERROR_WAS_STILL_DRAWING && FAILED(hr)) { WARN_LOG(RENDERER, "Present failed %x", hr); + } } void DX11Context::EndImGuiFrame() @@ -275,9 +281,9 @@ void DX11Context::resize() pDeviceContext->OMSetRenderTargets(1, &nullRTV, nullptr); renderTargetView.reset(); #ifdef TARGET_UWP - HRESULT hr = swapchain->ResizeBuffers(2, settings.display.width, settings.display.height, DXGI_FORMAT_R8G8B8A8_UNORM, 0); + HRESULT hr = swapchain->ResizeBuffers(2, settings.display.width, settings.display.height, DXGI_FORMAT_R8G8B8A8_UNORM, allowTearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0); #else - HRESULT hr = swapchain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH); + HRESULT hr = swapchain->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH | (allowTearing ? DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING : 0)); if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) { handleDeviceLost(); diff --git a/core/rend/dx11/dx11context.h b/core/rend/dx11/dx11context.h index 9067d85cf..260264545 100644 --- a/core/rend/dx11/dx11context.h +++ b/core/rend/dx11/dx11context.h @@ -86,6 +86,7 @@ private: ComPtr renderTargetView; bool overlayOnly = false; DX11Overlay overlay; + bool allowTearing = false; bool swapOnVSync = false; bool frameRendered = false; std::string adapterDesc; From 2797cca5ff3867a53054a1e8e1cd91fb8d84896d Mon Sep 17 00:00:00 2001 From: Edward Li Date: Sat, 18 May 2024 04:30:08 +0800 Subject: [PATCH 81/86] Always use BSD sed --- shell/apple/generate_xcode_project.command | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/apple/generate_xcode_project.command b/shell/apple/generate_xcode_project.command index 13a16475d..311c39ef1 100755 --- a/shell/apple/generate_xcode_project.command +++ b/shell/apple/generate_xcode_project.command @@ -34,5 +34,5 @@ fi cmake -B build -DCMAKE_BUILD_TYPE=Release $option -DCMAKE_XCODE_GENERATE_SCHEME=YES -G "Xcode" nl=$'\n' -sed -i '' -E "s/launchStyle/customLLDBInitFile = \"\$(SRCROOT)\/shell\/apple\/\\${lldbinitfolder}\/LLDBInitFile\"\\${nl}launchStyle/g" build/flycast.xcodeproj/xcshareddata/xcschemes/flycast.xcscheme +/usr/bin/sed -i '' -E "s/launchStyle/customLLDBInitFile = \"\$(SRCROOT)\/shell\/apple\/\\${lldbinitfolder}\/LLDBInitFile\"\\${nl}launchStyle/g" build/flycast.xcodeproj/xcshareddata/xcschemes/flycast.xcscheme open build/flycast.xcodeproj From 6b18ad7e3e83ac6b28d0775acc877983e5d55d8f Mon Sep 17 00:00:00 2001 From: scribam Date: Fri, 17 May 2024 18:06:40 +0200 Subject: [PATCH 82/86] cmake: better version conversion for uwp --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d2c6fc0a7..928667710 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,11 +105,11 @@ if(GIT_FOUND AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/.git") string(REGEX REPLACE "[Vv]" "" MS_VERSION ${GIT_VERSION}) string(REPLACE "-" "." MS_VERSION ${MS_VERSION}) string(REGEX REPLACE "\.g[0-9a-f]+" "" MS_VERSION ${MS_VERSION}) - string(REGEX MATCH "[0-9]+\.[0-9]+\.[0-9]+" VERSION_3PARTS ${MS_VERSION}) - string(REGEX MATCH "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" VERSION_4PARTS ${MS_VERSION}) - if(VERSION_3PARTS STREQUAL "") + string(REGEX MATCHALL "[0-9]+" VERSION_PARTS ${MS_VERSION}) + list(LENGTH VERSION_PARTS VERSION_PARTS_LENGTH) + if(VERSION_PARTS_LENGTH EQUAL 2) string(APPEND MS_VERSION ".0.0") - elseif(VERSION_4PARTS STREQUAL "") + elseif(VERSION_PARTS_LENGTH EQUAL 3) string(APPEND MS_VERSION ".0") endif() endif() From 72e892a77e6d5811a96661d046203c878661de6a Mon Sep 17 00:00:00 2001 From: scribam Date: Sun, 19 May 2024 20:13:43 +0200 Subject: [PATCH 83/86] deps: update imgui to version 1.90.6 --- core/deps/imgui/backends/imgui_impl_dx11.cpp | 2 +- core/deps/imgui/backends/imgui_impl_dx9.cpp | 2 +- .../deps/imgui/backends/imgui_impl_vulkan.cpp | 16 +- core/deps/imgui/backends/imgui_impl_vulkan.h | 11 +- core/deps/imgui/imgui.cpp | 534 +++++++++++------- core/deps/imgui/imgui.h | 183 +++--- core/deps/imgui/imgui_demo.cpp | 105 +++- core/deps/imgui/imgui_draw.cpp | 351 +++++++++++- core/deps/imgui/imgui_internal.h | 55 +- core/deps/imgui/imgui_tables.cpp | 122 ++-- core/deps/imgui/imgui_widgets.cpp | 98 ++-- 11 files changed, 1062 insertions(+), 417 deletions(-) diff --git a/core/deps/imgui/backends/imgui_impl_dx11.cpp b/core/deps/imgui/backends/imgui_impl_dx11.cpp index 6bd023881..6ae2658bc 100644 --- a/core/deps/imgui/backends/imgui_impl_dx11.cpp +++ b/core/deps/imgui/backends/imgui_impl_dx11.cpp @@ -619,7 +619,7 @@ void ImGui_ImplDX11_Shutdown() void ImGui_ImplDX11_NewFrame() { ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); - IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX11_Init()?"); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?"); if (!bd->pFontSampler) ImGui_ImplDX11_CreateDeviceObjects(); diff --git a/core/deps/imgui/backends/imgui_impl_dx9.cpp b/core/deps/imgui/backends/imgui_impl_dx9.cpp index 9e14bf7d0..0eb4a61e3 100644 --- a/core/deps/imgui/backends/imgui_impl_dx9.cpp +++ b/core/deps/imgui/backends/imgui_impl_dx9.cpp @@ -401,7 +401,7 @@ void ImGui_ImplDX9_InvalidateDeviceObjects() void ImGui_ImplDX9_NewFrame() { ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); - IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplDX9_Init()?"); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?"); if (!bd->FontTexture.d3dTexture) ImGui_ImplDX9_CreateDeviceObjects(); diff --git a/core/deps/imgui/backends/imgui_impl_vulkan.cpp b/core/deps/imgui/backends/imgui_impl_vulkan.cpp index a8962ecd2..7d1c230c4 100644 --- a/core/deps/imgui/backends/imgui_impl_vulkan.cpp +++ b/core/deps/imgui/backends/imgui_impl_vulkan.cpp @@ -33,6 +33,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2024-04-19: Vulkan: Added convenience support for Volk via IMGUI_IMPL_VULKAN_USE_VOLK define (you can also use IMGUI_IMPL_VULKAN_NO_PROTOTYPES + wrap Volk via ImGui_ImplVulkan_LoadFunctions().) // 2024-02-14: *BREAKING CHANGE*: Moved RenderPass parameter from ImGui_ImplVulkan_Init() function to ImGui_ImplVulkan_InitInfo structure. Not required when using dynamic rendering. // 2024-02-12: *BREAKING CHANGE*: Dynamic rendering now require filling PipelineRenderingCreateInfo structure. // 2024-01-19: Vulkan: Fixed vkAcquireNextImageKHR() validation errors in VulkanSDK 1.3.275 by allocating one extra semaphore than in-flight frames. (#7236) @@ -108,12 +109,13 @@ void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_devi // Vulkan prototypes for use with custom loaders // (see description of IMGUI_IMPL_VULKAN_NO_PROTOTYPES in imgui_impl_vulkan.h -#ifdef VK_NO_PROTOTYPES +#if defined(VK_NO_PROTOTYPES) && !defined(VOLK_H_) +#define IMGUI_IMPL_VULKAN_USE_LOADER static bool g_FunctionsLoaded = false; #else static bool g_FunctionsLoaded = true; #endif -#ifdef VK_NO_PROTOTYPES +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER #define IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_MAP_MACRO) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkAllocateCommandBuffers) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkAllocateDescriptorSets) \ @@ -184,7 +186,7 @@ static bool g_FunctionsLoaded = true; #define IMGUI_VULKAN_FUNC_DEF(func) static PFN_##func func; IMGUI_VULKAN_FUNC_MAP(IMGUI_VULKAN_FUNC_DEF) #undef IMGUI_VULKAN_FUNC_DEF -#endif // VK_NO_PROTOTYPES +#endif // IMGUI_IMPL_VULKAN_USE_LOADER #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING static PFN_vkCmdBeginRenderingKHR ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR; @@ -1048,8 +1050,8 @@ bool ImGui_ImplVulkan_LoadFunctions(PFN_vkVoidFunction(*loader_func)(const ch // Load function pointers // You can use the default Vulkan loader using: // ImGui_ImplVulkan_LoadFunctions([](const char* function_name, void*) { return vkGetInstanceProcAddr(your_vk_isntance, function_name); }); - // But this would be equivalent to not setting VK_NO_PROTOTYPES. -#ifdef VK_NO_PROTOTYPES + // But this would be roughly equivalent to not setting VK_NO_PROTOTYPES. +#ifdef IMGUI_IMPL_VULKAN_USE_LOADER #define IMGUI_VULKAN_FUNC_LOAD(func) \ func = reinterpret_cast(loader_func(#func, user_data)); \ if (func == nullptr) \ @@ -1078,7 +1080,7 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) if (info->UseDynamicRendering) { #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING -#ifndef VK_NO_PROTOTYPES +#ifndef IMGUI_IMPL_VULKAN_USE_LOADER ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR = reinterpret_cast(vkGetInstanceProcAddr(info->Instance, "vkCmdBeginRenderingKHR")); ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR = reinterpret_cast(vkGetInstanceProcAddr(info->Instance, "vkCmdEndRenderingKHR")); #endif @@ -1131,7 +1133,7 @@ void ImGui_ImplVulkan_Shutdown() void ImGui_ImplVulkan_NewFrame() { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - IM_ASSERT(bd != nullptr && "Did you call ImGui_ImplVulkan_Init()?"); + IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplVulkan_Init()?"); if (!bd->FontDescriptorSet) ImGui_ImplVulkan_CreateFontsTexture(); diff --git a/core/deps/imgui/backends/imgui_impl_vulkan.h b/core/deps/imgui/backends/imgui_impl_vulkan.h index f77fc235b..c174a6ca1 100644 --- a/core/deps/imgui/backends/imgui_impl_vulkan.h +++ b/core/deps/imgui/backends/imgui_impl_vulkan.h @@ -42,13 +42,20 @@ // If you have no idea what this is, leave it alone! //#define IMGUI_IMPL_VULKAN_NO_PROTOTYPES -// Vulkan includes +// Convenience support for Volk +// (you can also technically use IMGUI_IMPL_VULKAN_NO_PROTOTYPES + wrap Volk via ImGui_ImplVulkan_LoadFunctions().) +//#define IMGUI_IMPL_VULKAN_USE_VOLK + #if defined(IMGUI_IMPL_VULKAN_NO_PROTOTYPES) && !defined(VK_NO_PROTOTYPES) #define VK_NO_PROTOTYPES #endif #if defined(VK_USE_PLATFORM_WIN32_KHR) && !defined(NOMINMAX) #define NOMINMAX -#include +#endif + +// Vulkan includes +#ifdef IMGUI_IMPL_VULKAN_USE_VOLK +#include #else #include #endif diff --git a/core/deps/imgui/imgui.cpp b/core/deps/imgui/imgui.cpp index 65fde5be2..00637d27a 100644 --- a/core/deps/imgui/imgui.cpp +++ b/core/deps/imgui/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.90.6 // (main code and documentation) // Help: @@ -7,15 +7,19 @@ // - Read top of imgui.cpp for more details, links and comments. // Resources: -// - FAQ https://dearimgui.com/faq -// - Getting Started https://dearimgui.com/getting-started -// - Homepage https://github.com/ocornut/imgui -// - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/6897 (please post your screenshots/video there!) -// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) -// - Glossary https://github.com/ocornut/imgui/wiki/Glossary -// - Issues & support https://github.com/ocornut/imgui/issues -// - Tests & Automation https://github.com/ocornut/imgui_test_engine +// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) +// - Homepage ................... https://github.com/ocornut/imgui +// - Releases & changelog ....... https://github.com/ocornut/imgui/releases +// - Gallery .................... https://github.com/ocornut/imgui/issues/7503 (please post your screenshots/video there!) +// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) +// - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) +// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui +// - Issues & support ........... https://github.com/ocornut/imgui/issues +// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) // For first-time users having issues compiling/linking/running/loading fonts: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. @@ -26,7 +30,7 @@ // See LICENSE.txt for copyright and licensing details (standard MIT License). // This library is free but needs your support to sustain development and maintenance. // Businesses: you can support continued development via B2B invoiced technical support, maintenance and sponsoring contracts. -// PLEASE reach out at omar AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Sponsors +// PLEASE reach out at omar AT dearimgui DOT com. See https://github.com/ocornut/imgui/wiki/Funding // Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine. // It is recommended that you don't modify imgui.cpp! It will become difficult for you to update the library. @@ -73,6 +77,7 @@ CODE // [SECTION] RENDER HELPERS // [SECTION] INITIALIZATION, SHUTDOWN // [SECTION] MAIN CODE (most of the code! lots of stuff, needs tidying up!) +// [SECTION] ID STACK // [SECTION] INPUTS // [SECTION] ERROR CHECKING // [SECTION] ITEM SUBMISSION @@ -425,6 +430,15 @@ CODE When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2024/04/18 (1.90.6) - TreeNode: Fixed a layout inconsistency when using an empty/hidden label followed by a SameLine() call. (#7505, #282) + - old: TreeNode("##Hidden"); SameLine(); Text("Hello"); // <-- This was actually incorrect! BUT appeared to look ok with the default style where ItemSpacing.x == FramePadding.x * 2 (it didn't look aligned otherwise). + - new: TreeNode("##Hidden"); SameLine(0, 0); Text("Hello"); // <-- This is correct for all styles values. + with the fix, IF you were successfully using TreeNode("")+SameLine(); you will now have extra spacing between your TreeNode and the following item. + You'll need to change the SameLine() call to SameLine(0,0) to remove this extraneous spacing. This seemed like the more sensible fix that's not making things less consistent. + (Note: when using this idiom you are likely to also use ImGuiTreeNodeFlags_SpanAvailWidth). + - 2024/03/18 (1.90.5) - merged the radius_x/radius_y parameters in ImDrawList::AddEllipse(), AddEllipseFilled() and PathEllipticalArcTo() into a single ImVec2 parameter. Exceptionally, because those functions were added in 1.90, we are not adding inline redirection functions. The transition is easy and should affect few users. (#2743, #7417) + - 2024/03/08 (1.90.5) - inputs: more formally obsoleted GetKeyIndex() when IMGUI_DISABLE_OBSOLETE_FUNCTIONS is set. It has been unnecessary and a no-op since 1.87 (it returns the same value as passed when used with a 1.87+ backend using io.AddKeyEvent() function). (#4921) + - IsKeyPressed(GetKeyIndex(ImGuiKey_XXX)) -> use IsKeyPressed(ImGuiKey_XXX) - 2024/01/15 (1.90.2) - commented out obsolete ImGuiIO::ImeWindowHandle marked obsolete in 1.87, favor of writing to 'void* ImGuiViewport::PlatformHandleRaw'. - 2023/12/19 (1.90.1) - commented out obsolete ImGuiKey_KeyPadEnter redirection to ImGuiKey_KeypadEnter. - 2023/11/06 (1.90.1) - removed CalcListClipping() marked obsolete in 1.86. Prefer using ImGuiListClipper which can return non-contiguous ranges. @@ -935,7 +949,7 @@ CODE A: - Businesses: please reach out to "omar AT dearimgui DOT com" if you work in a place using Dear ImGui! We can discuss ways for your company to fund development via invoiced technical support, maintenance or sponsoring contacts. This is among the most useful thing you can do for Dear ImGui. With increased funding, we sustain and grow work on this project. - Also see https://github.com/ocornut/imgui/wiki/Sponsors + >>> See https://github.com/ocornut/imgui/wiki/Funding - Businesses: you can also purchase licenses for the Dear ImGui Automation/Test Engine. - If you are experienced with Dear ImGui and C++, look at the GitHub issues, look at the Wiki, and see how you want to help and can help! - Disclose your usage of Dear ImGui via a dev blog post, a tweet, a screenshot, a mention somewhere etc. @@ -1026,6 +1040,7 @@ CODE #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" // warning: zero as null pointer constant // some standard header variations use #define NULL 0 #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) // We disable -Wpragmas because GCC doesn't provide a has_warning equivalent and some forks/patches may not follow the warning/version association. #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind @@ -1124,6 +1139,7 @@ static void RenderWindowDecorations(ImGuiWindow* window, const ImRec static void RenderWindowTitleBarContents(ImGuiWindow* window, const ImRect& title_bar_rect, const char* name, bool* p_open); static void RenderDimmedBackgroundBehindWindow(ImGuiWindow* window, ImU32 col); static void RenderDimmedBackgrounds(); +static void SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect); // Viewports const ImGuiID IMGUI_VIEWPORT_DEFAULT_ID = 0x11111111; // Using an arbitrary constant instead of e.g. ImHashStr("ViewportDefault", 0); so it's easier to spot in the debugger. The exact value doesn't matter. @@ -1179,58 +1195,59 @@ static void* GImAllocatorUserData = NULL; ImGuiStyle::ImGuiStyle() { - Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. - DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. - WindowPadding = ImVec2(8,8); // Padding within a window - WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. - WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. - WindowMinSize = ImVec2(32,32); // Minimum window size - WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text - WindowMenuButtonPosition= ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left. - ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows - ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. - PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows - PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. - FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) - FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). - FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. - ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines - ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) - CellPadding = ImVec2(4,2); // Padding within a table cell. CellPadding.y may be altered between different rows. - TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! - IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). - ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). - ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar - ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar - GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar - GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. - LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. - TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. - TabBorderSize = 0.0f; // Thickness of border around tabs. - TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. - TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. - TableAngledHeadersAngle = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees). - ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. - ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. - SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. - SeparatorTextBorderSize = 3.0f; // Thickkness of border in SeparatorText() - SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). - SeparatorTextPadding = ImVec2(20.0f,3.f);// Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. - DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. - DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. - MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. - AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. - AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). - AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). - CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. - CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. + Alpha = 1.0f; // Global alpha applies to everything in Dear ImGui. + DisabledAlpha = 0.60f; // Additional alpha multiplier applied by BeginDisabled(). Multiply over current value of Alpha. + WindowPadding = ImVec2(8,8); // Padding within a window + WindowRounding = 0.0f; // Radius of window corners rounding. Set to 0.0f to have rectangular windows. Large values tend to lead to variety of artifacts and are not recommended. + WindowBorderSize = 1.0f; // Thickness of border around windows. Generally set to 0.0f or 1.0f. Other values not well tested. + WindowMinSize = ImVec2(32,32); // Minimum window size + WindowTitleAlign = ImVec2(0.0f,0.5f);// Alignment for title bar text + WindowMenuButtonPosition = ImGuiDir_Left; // Position of the collapsing/docking button in the title bar (left/right). Defaults to ImGuiDir_Left. + ChildRounding = 0.0f; // Radius of child window corners rounding. Set to 0.0f to have rectangular child windows + ChildBorderSize = 1.0f; // Thickness of border around child windows. Generally set to 0.0f or 1.0f. Other values not well tested. + PopupRounding = 0.0f; // Radius of popup window corners rounding. Set to 0.0f to have rectangular child windows + PopupBorderSize = 1.0f; // Thickness of border around popup or tooltip windows. Generally set to 0.0f or 1.0f. Other values not well tested. + FramePadding = ImVec2(4,3); // Padding within a framed rectangle (used by most widgets) + FrameRounding = 0.0f; // Radius of frame corners rounding. Set to 0.0f to have rectangular frames (used by most widgets). + FrameBorderSize = 0.0f; // Thickness of border around frames. Generally set to 0.0f or 1.0f. Other values not well tested. + ItemSpacing = ImVec2(8,4); // Horizontal and vertical spacing between widgets/lines + ItemInnerSpacing = ImVec2(4,4); // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label) + CellPadding = ImVec2(4,2); // Padding within a table cell. Cellpadding.x is locked for entire table. CellPadding.y may be altered between different rows. + TouchExtraPadding = ImVec2(0,0); // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! + IndentSpacing = 21.0f; // Horizontal spacing when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). + ColumnsMinSpacing = 6.0f; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). + ScrollbarSize = 14.0f; // Width of the vertical scrollbar, Height of the horizontal scrollbar + ScrollbarRounding = 9.0f; // Radius of grab corners rounding for scrollbar + GrabMinSize = 12.0f; // Minimum width/height of a grab box for slider/scrollbar + GrabRounding = 0.0f; // Radius of grabs corners rounding. Set to 0.0f to have rectangular slider grabs. + LogSliderDeadzone = 4.0f; // The size in pixels of the dead-zone around zero on logarithmic sliders that cross zero. + TabRounding = 4.0f; // Radius of upper corners of a tab. Set to 0.0f to have rectangular tabs. + TabBorderSize = 0.0f; // Thickness of border around tabs. + TabMinWidthForCloseButton = 0.0f; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. + TabBarBorderSize = 1.0f; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. + TableAngledHeadersAngle = 35.0f * (IM_PI / 180.0f); // Angle of angled headers (supported values range from -50 degrees to +50 degrees). + TableAngledHeadersTextAlign = ImVec2(0.5f,0.0f);// Alignment of angled headers within the cell + ColorButtonPosition = ImGuiDir_Right; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. + ButtonTextAlign = ImVec2(0.5f,0.5f);// Alignment of button text when button is larger than text. + SelectableTextAlign = ImVec2(0.0f,0.0f);// Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. + SeparatorTextBorderSize = 3.0f; // Thickkness of border in SeparatorText() + SeparatorTextAlign = ImVec2(0.0f,0.5f);// Alignment of text within the separator. Defaults to (0.0f, 0.5f) (left aligned, center). + SeparatorTextPadding = ImVec2(20.0f,3.f);// Horizontal offset of text from each edge of the separator + spacing on other axis. Generally small values. .y is recommended to be == FramePadding.y. + DisplayWindowPadding = ImVec2(19,19); // Window position are clamped to be visible within the display area or monitors by at least this amount. Only applies to regular windows. + DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. + MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. + AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. + AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). + AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). + CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. + CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Behaviors - HoverStationaryDelay = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. - HoverDelayShort = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. - HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " - HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. - HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. + HoverStationaryDelay = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. + HoverDelayShort = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. + HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " + HoverFlagsForTooltipMouse = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayShort | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using mouse. + HoverFlagsForTooltipNav = ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_AllowWhenDisabled; // Default flags when using IsItemHovered(ImGuiHoveredFlags_ForTooltip) or BeginItemTooltip()/SetItemTooltip() while using keyboard/gamepad. // Default theme ImGui::StyleColorsDark(this); @@ -2318,6 +2335,20 @@ const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const cha return in_text_start; } +int ImTextCountLines(const char* in_text, const char* in_text_end) +{ + if (in_text_end == NULL) + in_text_end = in_text + strlen(in_text); // FIXME-OPT: Not optimal approach, discourage use for now. + int count = 0; + while (in_text < in_text_end) + { + const char* line_end = (const char*)memchr(in_text, '\n', in_text_end - in_text); + in_text = line_end ? line_end + 1 : in_text_end; + count++; + } + return count; +} + IM_MSVC_RUNTIME_CHECKS_RESTORE //----------------------------------------------------------------------------- @@ -3100,35 +3131,38 @@ void ImGui::PopStyleColor(int count) static const ImGuiDataVarInfo GStyleVarInfo[] = { - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) }, // ImGuiStyleVar_DisabledAlpha - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign - { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)},// ImGuiStyleVar_SeparatorTextBorderSize - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign - { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, Alpha) }, // ImGuiStyleVar_Alpha + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, DisabledAlpha) }, // ImGuiStyleVar_DisabledAlpha + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowPadding) }, // ImGuiStyleVar_WindowPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowRounding) }, // ImGuiStyleVar_WindowRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, WindowBorderSize) }, // ImGuiStyleVar_WindowBorderSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowMinSize) }, // ImGuiStyleVar_WindowMinSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, WindowTitleAlign) }, // ImGuiStyleVar_WindowTitleAlign + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildRounding) }, // ImGuiStyleVar_ChildRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ChildBorderSize) }, // ImGuiStyleVar_ChildBorderSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupRounding) }, // ImGuiStyleVar_PopupRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, PopupBorderSize) }, // ImGuiStyleVar_PopupBorderSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, FramePadding) }, // ImGuiStyleVar_FramePadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameRounding) }, // ImGuiStyleVar_FrameRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, FrameBorderSize) }, // ImGuiStyleVar_FrameBorderSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemSpacing) }, // ImGuiStyleVar_ItemSpacing + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ItemInnerSpacing) }, // ImGuiStyleVar_ItemInnerSpacing + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, IndentSpacing) }, // ImGuiStyleVar_IndentSpacing + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, CellPadding) }, // ImGuiStyleVar_CellPadding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarSize) }, // ImGuiStyleVar_ScrollbarSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, ScrollbarRounding) }, // ImGuiStyleVar_ScrollbarRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabMinSize) }, // ImGuiStyleVar_GrabMinSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, GrabRounding) }, // ImGuiStyleVar_GrabRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabRounding) }, // ImGuiStyleVar_TabRounding + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBorderSize) }, // ImGuiStyleVar_TabBorderSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TabBarBorderSize) }, // ImGuiStyleVar_TabBarBorderSize + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersAngle)}, // ImGuiStyleVar_TableAngledHeadersAngle + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, TableAngledHeadersTextAlign)},// ImGuiStyleVar_TableAngledHeadersTextAlign + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, ButtonTextAlign) }, // ImGuiStyleVar_ButtonTextAlign + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SelectableTextAlign) }, // ImGuiStyleVar_SelectableTextAlign + { ImGuiDataType_Float, 1, (ImU32)offsetof(ImGuiStyle, SeparatorTextBorderSize)}, // ImGuiStyleVar_SeparatorTextBorderSize + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextAlign) }, // ImGuiStyleVar_SeparatorTextAlign + { ImGuiDataType_Float, 2, (ImU32)offsetof(ImGuiStyle, SeparatorTextPadding) }, // ImGuiStyleVar_SeparatorTextPadding }; const ImGuiDataVarInfo* ImGui::GetStyleVarInfo(ImGuiStyleVar idx) @@ -3773,45 +3807,6 @@ ImGuiWindow::~ImGuiWindow() ColumnsStorage.clear_destruct(); } -ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) -{ - ImGuiID seed = IDStack.back(); - ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); - ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) - ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); - return id; -} - -ImGuiID ImGuiWindow::GetID(const void* ptr) -{ - ImGuiID seed = IDStack.back(); - ImGuiID id = ImHashData(&ptr, sizeof(void*), seed); - ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) - ImGui::DebugHookIdInfo(id, ImGuiDataType_Pointer, ptr, NULL); - return id; -} - -ImGuiID ImGuiWindow::GetID(int n) -{ - ImGuiID seed = IDStack.back(); - ImGuiID id = ImHashData(&n, sizeof(n), seed); - ImGuiContext& g = *Ctx; - if (g.DebugHookIdInfo == id) - ImGui::DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); - return id; -} - -// This is only used in rare/specific situations to manufacture an ID out of nowhere. -ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) -{ - ImGuiID seed = IDStack.back(); - ImRect r_rel = ImGui::WindowRectAbsToRel(this, r_abs); - ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed); - return id; -} - static void SetCurrentWindow(ImGuiWindow* window) { ImGuiContext& g = *GImGui; @@ -4554,6 +4549,27 @@ void ImGui::UpdateHoveredWindowAndCaptureFlags() io.WantTextInput = (g.WantTextInputNextFrame != -1) ? (g.WantTextInputNextFrame != 0) : false; } +// Calling SetupDrawListSharedData() is followed by SetCurrentFont() which sets up the remaining data. +static void SetupDrawListSharedData() +{ + ImGuiContext& g = *GImGui; + ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); + for (ImGuiViewportP* viewport : g.Viewports) + virtual_space.Add(viewport->GetMainRect()); + g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); + g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; + g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); + g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; + if (g.Style.AntiAliasedLines) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; + if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines)) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; + if (g.Style.AntiAliasedFill) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; + if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; +} + void ImGui::NewFrame() { IM_ASSERT(GImGui != NULL && "No current context. Did you call ImGui::CreateContext() and ImGui::SetCurrentContext() ?"); @@ -4596,23 +4612,9 @@ void ImGui::NewFrame() // Setup current font and draw list shared data g.IO.Fonts->Locked = true; + SetupDrawListSharedData(); SetCurrentFont(GetDefaultFont()); IM_ASSERT(g.Font->IsLoaded()); - ImRect virtual_space(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - for (ImGuiViewportP* viewport : g.Viewports) - virtual_space.Add(viewport->GetMainRect()); - g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); - g.DrawListSharedData.CurveTessellationTol = g.Style.CurveTessellationTol; - g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); - g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; - if (g.Style.AntiAliasedLines) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; - if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; - if (g.Style.AntiAliasedFill) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; - if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; // Mark rendering data as invalid to prevent user who may have a handle on it to use it. for (ImGuiViewportP* viewport : g.Viewports) @@ -5893,7 +5895,7 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si int ret_auto_fit_mask = 0x00; const float grip_draw_size = IM_TRUNC(ImMax(g.FontSize * 1.35f, window->WindowRounding + 1.0f + g.FontSize * 0.2f)); - const float grip_hover_inner_size = IM_TRUNC(grip_draw_size * 0.75f); + const float grip_hover_inner_size = (resize_grip_count > 0) ? IM_TRUNC(grip_draw_size * 0.75f) : 0.0f; const float grip_hover_outer_size = g.IO.ConfigWindowsResizeFromEdges ? WINDOWS_HOVER_PADDING : 0.0f; ImRect clamp_rect = visibility_rect; @@ -6021,10 +6023,13 @@ static int ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& si border_target = ImClamp(border_target, clamp_min, clamp_max); if (flags & ImGuiWindowFlags_ChildWindow) // Clamp resizing of childs within parent { - if ((flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (flags & ImGuiWindowFlags_NoScrollbar)) - border_target.x = ImClamp(border_target.x, window->ParentWindow->InnerClipRect.Min.x, window->ParentWindow->InnerClipRect.Max.x); - if (flags & ImGuiWindowFlags_NoScrollbar) - border_target.y = ImClamp(border_target.y, window->ParentWindow->InnerClipRect.Min.y, window->ParentWindow->InnerClipRect.Max.y); + ImGuiWindowFlags parent_flags = window->ParentWindow->Flags; + ImRect border_limit_rect = window->ParentWindow->InnerRect; + border_limit_rect.Expand(ImVec2(-ImMax(window->WindowPadding.x, window->WindowBorderSize), -ImMax(window->WindowPadding.y, window->WindowBorderSize))); + if ((parent_flags & (ImGuiWindowFlags_HorizontalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar)) == 0 || (parent_flags & ImGuiWindowFlags_NoScrollbar)) + border_target.x = ImClamp(border_target.x, border_limit_rect.Min.x, border_limit_rect.Max.x); + if (parent_flags & ImGuiWindowFlags_NoScrollbar) + border_target.y = ImClamp(border_target.y, border_limit_rect.Min.y, border_limit_rect.Max.y); } if (!ignore_resize) CalcResizePosSizeFromAnyCorner(window, border_target, ImMin(def.SegmentN1, def.SegmentN2), &pos_target, &size_target); @@ -6330,6 +6335,30 @@ void ImGui::UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags } } +// [EXPERIMENTAL] Called by Begin(). NextWindowData is valid at this point. +// This is designed as a toy/test-bed for +void ImGui::UpdateWindowSkipRefresh(ImGuiWindow* window) +{ + ImGuiContext& g = *GImGui; + window->SkipRefresh = false; + if ((g.NextWindowData.Flags & ImGuiNextWindowDataFlags_HasRefreshPolicy) == 0) + return; + if (g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_TryToAvoidRefresh) + { + // FIXME-IDLE: Tests for e.g. mouse clicks or keyboard while focused. + if (window->Appearing) // If currently appearing + return; + if (window->Hidden) // If was hidden (previous frame) + return; + if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnHover) && g.HoveredWindow && window->RootWindow == g.HoveredWindow->RootWindow) + return; + if ((g.NextWindowData.RefreshFlagsVal & ImGuiWindowRefreshFlags_RefreshOnFocus) && g.NavWindow && window->RootWindow == g.NavWindow->RootWindow) + return; + window->DrawList = NULL; + window->SkipRefresh = true; + } +} + // When a modal popup is open, newly created windows that want focus (i.e. are not popups and do not specify ImGuiWindowFlags_NoFocusOnAppearing) // should be positioned behind that modal window, unless the window was created inside the modal begin-stack. // In case of multiple stacked modals newly created window honors begin stack order and does not go below its own modal parent. @@ -6464,7 +6493,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) PushFocusScope((flags & ImGuiWindowFlags_NavFlattened) ? g.CurrentFocusScopeId : window->ID); window->NavRootFocusScopeId = g.CurrentFocusScopeId; - // Add to popup stack + // Add to popup stacks: update OpenPopupStack[] data, push to BeginPopupStack[] if (flags & ImGuiWindowFlags_Popup) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; @@ -6528,11 +6557,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) if (window->Appearing) SetWindowConditionAllowFlags(window, ImGuiCond_Appearing, false); + // [EXPERIMENTAL] Skip Refresh mode + UpdateWindowSkipRefresh(window); + // We intentionally set g.CurrentWindow to NULL to prevent usage until when the viewport is set, then will call SetCurrentWindow() g.CurrentWindow = NULL; // When reusing window again multiple times a frame, just append content (don't need to setup again) - if (first_begin_of_the_frame) + if (first_begin_of_the_frame && !window->SkipRefresh) { // Initialize const bool window_is_child_tooltip = (flags & ImGuiWindowFlags_ChildWindow) && (flags & ImGuiWindowFlags_Tooltip); // FIXME-WIP: Undocumented behavior of Child+Tooltip for pinned tooltip (#1345) @@ -6611,8 +6643,14 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->DC.MenuBarOffset.x = ImMax(ImMax(window->WindowPadding.x, style.ItemSpacing.x), g.NextWindowData.MenuBarOffsetMinVal.x); window->DC.MenuBarOffset.y = g.NextWindowData.MenuBarOffsetMinVal.y; + // Depending on condition we use previous or current window size to compare against contents size to decide if a scrollbar should be visible. + // Those flags will be altered further down in the function depending on more conditions. bool use_current_size_for_scrollbar_x = window_just_created; bool use_current_size_for_scrollbar_y = window_just_created; + if (window_size_x_set_by_api && window->ContentSizeExplicit.x != 0.0f) + use_current_size_for_scrollbar_x = true; + if (window_size_y_set_by_api && window->ContentSizeExplicit.y != 0.0f) // #7252 + use_current_size_for_scrollbar_y = true; // Collapse window by double-clicking on title bar // At this point we don't have a clipping rectangle setup yet, so we can use the title bar area for hit detection and drawing @@ -6620,8 +6658,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { // We don't use a regular button+id to test for double-click on title bar (mostly due to legacy reason, could be fixed), so verify that we don't have items over the title bar. ImRect title_bar_rect = window->TitleBarRect(); - if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max) && g.IO.MouseClickedCount[0] == 2) - window->WantCollapseToggle = true; + if (g.HoveredWindow == window && g.HoveredId == 0 && g.HoveredIdPreviousFrame == 0 && IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max)) + if (g.IO.MouseClickedCount[0] == 2 && GetKeyOwner(ImGuiKey_MouseLeft) == ImGuiKeyOwner_None) + window->WantCollapseToggle = true; if (window->WantCollapseToggle) { window->Collapsed = !window->Collapsed; @@ -6827,17 +6866,19 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) window->InnerRect.Max.y = window->Pos.y + window->Size.y - window->DecoOuterSizeY2; // Inner clipping rectangle. - // Will extend a little bit outside the normal work region. - // This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space. - // Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. + // - Extend a outside of normal work region up to borders. + // - This is to allow e.g. Selectable or CollapsingHeader or some separators to cover that space. + // - It also makes clipped items be more noticeable. + // - And is consistent on both axis (prior to 2024/05/03 ClipRect used WindowPadding.x * 0.5f on left and right edge), see #3312 + // - Force round operator last to ensure that e.g. (int)(max.x-min.x) in user's render code produce correct result. // Note that if our window is collapsed we will end up with an inverted (~null) clipping rectangle which is the correct behavior. // Affected by window/frame border size. Used by: // - Begin() initial clip rect float top_border_size = (((flags & ImGuiWindowFlags_MenuBar) || !(flags & ImGuiWindowFlags_NoTitleBar)) ? style.FrameBorderSize : window->WindowBorderSize); - window->InnerClipRect.Min.x = ImTrunc(0.5f + window->InnerRect.Min.x + ImMax(ImTrunc(window->WindowPadding.x * 0.5f), window->WindowBorderSize)); - window->InnerClipRect.Min.y = ImTrunc(0.5f + window->InnerRect.Min.y + top_border_size); - window->InnerClipRect.Max.x = ImTrunc(0.5f + window->InnerRect.Max.x - ImMax(ImTrunc(window->WindowPadding.x * 0.5f), window->WindowBorderSize)); - window->InnerClipRect.Max.y = ImTrunc(0.5f + window->InnerRect.Max.y - window->WindowBorderSize); + window->InnerClipRect.Min.x = ImFloor(0.5f + window->InnerRect.Min.x + window->WindowBorderSize); + window->InnerClipRect.Min.y = ImFloor(0.5f + window->InnerRect.Min.y + top_border_size); + window->InnerClipRect.Max.x = ImFloor(0.5f + window->InnerRect.Max.x - window->WindowBorderSize); + window->InnerClipRect.Max.y = ImFloor(0.5f + window->InnerRect.Max.y - window->WindowBorderSize); window->InnerClipRect.ClipWithFull(host_rect); // Default item width. Make it proportional to window size if window manually resizes @@ -6998,7 +7039,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // We fill last item data based on Title Bar/Tab, in order for IsItemHovered() and IsItemActive() to be usable after Begin(). // This is useful to allow creating context menus on title bar only, etc. - SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(title_bar_rect.Min, title_bar_rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, title_bar_rect); + SetLastItemDataForWindow(window, title_bar_rect); // [DEBUG] #ifndef IMGUI_DISABLE_DEBUG_TOOLS @@ -7014,11 +7055,17 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) } else { + // Skip refresh always mark active + if (window->SkipRefresh) + window->Active = true; + // Append SetCurrentWindow(window); + SetLastItemDataForWindow(window, window->TitleBarRect()); } - PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); + if (!window->SkipRefresh) + PushClipRect(window->InnerClipRect.Min, window->InnerClipRect.Max, true); // Clear 'accessed' flag last thing (After PushClipRect which will set the flag. We want the flag to stay false when the default "Debug" window is unused) window->WriteAccessed = false; @@ -7026,7 +7073,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) g.NextWindowData.ClearFlags(); // Update visibility - if (first_begin_of_the_frame) + if (first_begin_of_the_frame && !window->SkipRefresh) { if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_ChildMenu)) { @@ -7072,6 +7119,11 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) skip_items = true; window->SkipItems = skip_items; } + else if (first_begin_of_the_frame) + { + // Skip refresh mode + window->SkipItems = true; + } // [DEBUG] io.ConfigDebugBeginReturnValue override return value to test Begin/End and BeginChild/EndChild behaviors. // (The implicit fallback window is NOT automatically ended allowing it to always be able to receive commands without crashing) @@ -7088,6 +7140,12 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) return !window->SkipItems; } +static void ImGui::SetLastItemDataForWindow(ImGuiWindow* window, const ImRect& rect) +{ + ImGuiContext& g = *GImGui; + SetLastItemData(window->MoveId, g.CurrentItemFlags, IsMouseHoveringRect(rect.Min, rect.Max, false) ? ImGuiItemStatusFlags_HoveredRect : 0, rect); +} + void ImGui::End() { ImGuiContext& g = *GImGui; @@ -7108,9 +7166,16 @@ void ImGui::End() // Close anything that is open if (window->DC.CurrentColumns) EndColumns(); - PopClipRect(); // Inner window clip rectangle + if (!window->SkipRefresh) + PopClipRect(); // Inner window clip rectangle PopFocusScope(); + if (window->SkipRefresh) + { + IM_ASSERT(window->DrawList == NULL); + window->DrawList = &window->DrawListInst; + } + // Stop logging if (!(window->Flags & ImGuiWindowFlags_ChildWindow)) // FIXME: add more options for scope of logging LogFinish(); @@ -7795,6 +7860,14 @@ void ImGui::SetNextWindowBgAlpha(float alpha) g.NextWindowData.BgAlphaVal = alpha; } +// This is experimental and meant to be a toy for exploring a future/wider range of features. +void ImGui::SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags) +{ + ImGuiContext& g = *GImGui; + g.NextWindowData.Flags |= ImGuiNextWindowDataFlags_HasRefreshPolicy; + g.NextWindowData.RefreshFlagsVal = flags; +} + ImDrawList* ImGui::GetWindowDrawList() { ImGuiWindow* window = GetCurrentWindow(); @@ -7965,6 +8038,69 @@ ImGuiStorage* ImGui::GetStateStorage() return window->DC.StateStorage; } +bool ImGui::IsRectVisible(const ImVec2& size) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); +} + +bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); +} + +//----------------------------------------------------------------------------- +// [SECTION] ID STACK +//----------------------------------------------------------------------------- + +// This is one of the very rare legacy case where we use ImGuiWindow methods, +// it should ideally be flattened at some point but it's been used a lots by widgets. +ImGuiID ImGuiWindow::GetID(const char* str, const char* str_end) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + ImGuiContext& g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetID(const void* ptr) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&ptr, sizeof(void*), seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + ImGuiContext& g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_Pointer, ptr, NULL); +#endif + return id; +} + +ImGuiID ImGuiWindow::GetID(int n) +{ + ImGuiID seed = IDStack.back(); + ImGuiID id = ImHashData(&n, sizeof(n), seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS + ImGuiContext& g = *Ctx; + if (g.DebugHookIdInfo == id) + ImGui::DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); +#endif + return id; +} + +// This is only used in rare/specific situations to manufacture an ID out of nowhere. +ImGuiID ImGuiWindow::GetIDFromRectangle(const ImRect& r_abs) +{ + ImGuiID seed = IDStack.back(); + ImRect r_rel = ImGui::WindowRectAbsToRel(this, r_abs); + ImGuiID id = ImHashData(&r_rel, sizeof(r_rel), seed); + return id; +} + void ImGui::PushID(const char* str_id) { ImGuiContext& g = *GImGui; @@ -8002,8 +8138,10 @@ void ImGui::PushOverrideID(ImGuiID id) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; +#ifndef IMGUI_DISABLE_DEBUG_TOOLS if (g.DebugHookIdInfo == id) DebugHookIdInfo(id, ImGuiDataType_ID, NULL, NULL); +#endif window->IDStack.push_back(id); } @@ -8013,18 +8151,22 @@ void ImGui::PushOverrideID(ImGuiID id) ImGuiID ImGui::GetIDWithSeed(const char* str, const char* str_end, ImGuiID seed) { ImGuiID id = ImHashStr(str, str_end ? (str_end - str) : 0, seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; if (g.DebugHookIdInfo == id) DebugHookIdInfo(id, ImGuiDataType_String, str, str_end); +#endif return id; } ImGuiID ImGui::GetIDWithSeed(int n, ImGuiID seed) { ImGuiID id = ImHashData(&n, sizeof(n), seed); +#ifndef IMGUI_DISABLE_DEBUG_TOOLS ImGuiContext& g = *GImGui; if (g.DebugHookIdInfo == id) DebugHookIdInfo(id, ImGuiDataType_S32, (void*)(intptr_t)n, NULL); +#endif return id; } @@ -8053,19 +8195,6 @@ ImGuiID ImGui::GetID(const void* ptr_id) return window->GetID(ptr_id); } -bool ImGui::IsRectVisible(const ImVec2& size) -{ - ImGuiWindow* window = GImGui->CurrentWindow; - return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); -} - -bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) -{ - ImGuiWindow* window = GImGui->CurrentWindow; - return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); -} - - //----------------------------------------------------------------------------- // [SECTION] INPUTS //----------------------------------------------------------------------------- @@ -9196,7 +9325,7 @@ void ImGui::SetNextFrameWantCaptureMouse(bool want_capture_mouse) #ifndef IMGUI_DISABLE_DEBUG_TOOLS static const char* GetInputSourceName(ImGuiInputSource source) { - const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad", "Clipboard" }; + const char* input_source_names[] = { "None", "Mouse", "Keyboard", "Gamepad" }; IM_ASSERT(IM_ARRAYSIZE(input_source_names) == ImGuiInputSource_COUNT && source >= 0 && source < ImGuiInputSource_COUNT); return input_source_names[source]; } @@ -9512,13 +9641,15 @@ bool ImGui::Shortcut(ImGuiKeyChord key_chord, ImGuiID owner_id, ImGuiInputFlags //----------------------------------------------------------------------------- // Helper function to verify ABI compatibility between caller code and compiled version of Dear ImGui. +// This is called by IMGUI_CHECKVERSION(). // Verify that the type sizes are matching between the calling file's compilation unit and imgui.cpp's compilation unit -// If this triggers you have an issue: -// - Most commonly: mismatched headers and compiled code version. -// - Or: mismatched configuration #define, compilation settings, packing pragma etc. -// The configuration settings mentioned in imconfig.h must be set for all compilation units involved with Dear ImGui, -// which is way it is required you put them in your imconfig file (and not just before including imgui.h). -// Otherwise it is possible that different compilation units would see different structure layout +// If this triggers you have mismatched headers and compiled code versions. +// - It could be because of a build issue (using new headers with old compiled code) +// - It could be because of mismatched configuration #define, compilation settings, packing pragma etc. +// THE CONFIGURATION SETTINGS MENTIONED IN imconfig.h MUST BE SET FOR ALL COMPILATION UNITS INVOLVED WITH DEAR IMGUI. +// Which is why it is required you put them in your imconfig file (and NOT only before including imgui.h). +// Otherwise it is possible that different compilation units would see different structure layout. +// If you don't want to modify imconfig.h you can use the IMGUI_USER_CONFIG define to change filename. bool ImGui::DebugCheckVersionAndDataLayout(const char* version, size_t sz_io, size_t sz_style, size_t sz_vec2, size_t sz_vec4, size_t sz_vert, size_t sz_idx) { bool error = false; @@ -10775,7 +10906,7 @@ void ImGui::OpenPopupEx(ImGuiID id, ImGuiPopupFlags popup_flags) ImGuiPopupData popup_ref; // Tagged as new ref as Window will be set back to NULL if we write this into OpenPopupStack. popup_ref.PopupId = id; popup_ref.Window = NULL; - popup_ref.BackupNavWindow = g.NavWindow; // When popup closes focus may be restored to NavWindow (depend on window type). + popup_ref.RestoreNavWindow = g.NavWindow; // When popup closes focus may be restored to NavWindow (depend on window type). popup_ref.OpenFrameCount = g.FrameCount; popup_ref.OpenParentId = parent_window->IDStack.back(); popup_ref.OpenPopupPos = NavCalcPreferredRefPos(); @@ -10824,6 +10955,7 @@ void ImGui::ClosePopupsOverWindow(ImGuiWindow* ref_window, bool restore_focus_to return; // Don't close our own child popup windows. + //IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupsOverWindow(\"%s\") restore_under=%d\n", ref_window ? ref_window->Name : "", restore_focus_to_window_under_popup); int popup_count_to_keep = 0; if (ref_window) { @@ -10880,18 +11012,19 @@ void ImGui::ClosePopupsExceptModals() void ImGui::ClosePopupToLevel(int remaining, bool restore_focus_to_window_under_popup) { ImGuiContext& g = *GImGui; - IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupToLevel(%d), restore_focus_to_window_under_popup=%d\n", remaining, restore_focus_to_window_under_popup); + IMGUI_DEBUG_LOG_POPUP("[popup] ClosePopupToLevel(%d), restore_under=%d\n", remaining, restore_focus_to_window_under_popup); IM_ASSERT(remaining >= 0 && remaining < g.OpenPopupStack.Size); // Trim open popup stack - ImGuiWindow* popup_window = g.OpenPopupStack[remaining].Window; - ImGuiWindow* popup_backup_nav_window = g.OpenPopupStack[remaining].BackupNavWindow; + ImGuiPopupData prev_popup = g.OpenPopupStack[remaining]; g.OpenPopupStack.resize(remaining); - if (restore_focus_to_window_under_popup) + // Restore focus (unless popup window was not yet submitted, and didn't have a chance to take focus anyhow. See #7325 for an edge case) + if (restore_focus_to_window_under_popup && prev_popup.Window) { - ImGuiWindow* focus_window = (popup_window && popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : popup_backup_nav_window; - if (focus_window && !focus_window->WasActive && popup_window) + ImGuiWindow* popup_window = prev_popup.Window; + ImGuiWindow* focus_window = (popup_window->Flags & ImGuiWindowFlags_ChildMenu) ? popup_window->ParentWindow : prev_popup.RestoreNavWindow; + if (focus_window && !focus_window->WasActive) FocusTopMostWindowUnderOne(popup_window, NULL, NULL, ImGuiFocusRequestFlags_RestoreFocusedChild); // Fallback else FocusWindow(focus_window, (g.NavLayer == ImGuiNavLayer_Main) ? ImGuiFocusRequestFlags_RestoreFocusedChild : ImGuiFocusRequestFlags_None); @@ -11351,7 +11484,7 @@ static bool ImGui::NavScoreItem(ImGuiNavItemData* result) // Compute distance between boxes // FIXME-NAV: Introducing biases for vertical navigation, needs to be removed. float dbx = NavScoreItemDistInterval(cand.Min.x, cand.Max.x, curr.Min.x, curr.Max.x); - float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.1f), ImLerp(cand.Min.y, cand.Max.y, 0.9f), ImLerp(curr.Min.y, curr.Max.y, 0.1f), ImLerp(curr.Min.y, curr.Max.y, 0.9f)); // Scale down on Y to keep using box-distance for vertically touching items + float dby = NavScoreItemDistInterval(ImLerp(cand.Min.y, cand.Max.y, 0.2f), ImLerp(cand.Min.y, cand.Max.y, 0.8f), ImLerp(curr.Min.y, curr.Max.y, 0.2f), ImLerp(curr.Min.y, curr.Max.y, 0.8f)); // Scale down on Y to keep using box-distance for vertically touching items if (dby != 0.0f && dbx != 0.0f) dbx = (dbx / 1000.0f) + ((dbx > 0.0f) ? +1.0f : -1.0f); float dist_box = ImFabs(dbx) + ImFabs(dby); @@ -12283,8 +12416,10 @@ void ImGui::NavMoveRequestApplyResult() g.NavLastValidSelectionUserData = ImGuiSelectionUserData_Invalid; } - // FIXME: Could become optional e.g. ImGuiNavMoveFlags_NoClearActiveId if we later want to apply navigation requests without altering active input. - if (g.ActiveId != result->ID) + // Clear active id unless requested not to + // FIXME: ImGuiNavMoveFlags_NoClearActiveId is currently unused as we don't have a clear strategy to preserve active id after interaction, + // so this is mostly provided as a gateway for further experiments (see #1418, #2890) + if (g.ActiveId != result->ID && (g.NavMoveFlags & ImGuiNavMoveFlags_NoClearActiveId) == 0) ClearActiveID(); // Don't set NavJustMovedToId if just landed on the same spot (which may happen with ImGuiNavMoveFlags_AllowCurrentNavId) @@ -12918,6 +13053,7 @@ bool ImGui::BeginDragDropSource(ImGuiDragDropFlags flags) source_drag_active = true; } + IM_ASSERT(g.DragDropWithinTarget == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() if (source_drag_active) { if (!g.DragDropActive) @@ -13033,7 +13169,7 @@ bool ImGui::BeginDragDropTargetCustom(const ImRect& bb, ImGuiID id) if (window->SkipItems) return false; - IM_ASSERT(g.DragDropWithinTarget == false); + IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() g.DragDropTargetRect = bb; g.DragDropTargetClipRect = window->ClipRect; // May want to be overriden by user depending on use case? g.DragDropTargetId = id; @@ -13068,7 +13204,7 @@ bool ImGui::BeginDragDropTarget() if (g.DragDropPayload.SourceId == id) return false; - IM_ASSERT(g.DragDropWithinTarget == false); + IM_ASSERT(g.DragDropWithinTarget == false && g.DragDropWithinSource == false); // Can't nest BeginDragDropSource() and BeginDragDropTarget() g.DragDropTargetRect = display_rect; g.DragDropTargetClipRect = (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HasClipRect) ? g.LastItemData.ClipRect : window->ClipRect; g.DragDropTargetId = id; @@ -14455,9 +14591,9 @@ void ImGui::ShowMetricsWindow(bool* p_open) { // As it's difficult to interact with tree nodes while popups are open, we display everything inline. ImGuiWindow* window = popup_data.Window; - BulletText("PopupID: %08x, Window: '%s' (%s%s), BackupNavWindow '%s', ParentWindow '%s'", + BulletText("PopupID: %08x, Window: '%s' (%s%s), RestoreNavWindow '%s', ParentWindow '%s'", popup_data.PopupId, window ? window->Name : "NULL", window && (window->Flags & ImGuiWindowFlags_ChildWindow) ? "Child;" : "", window && (window->Flags & ImGuiWindowFlags_ChildMenu) ? "Menu;" : "", - popup_data.BackupNavWindow ? popup_data.BackupNavWindow->Name : "NULL", window && window->ParentWindow ? window->ParentWindow->Name : "NULL"); + popup_data.RestoreNavWindow ? popup_data.RestoreNavWindow->Name : "NULL", window && window->ParentWindow ? window->ParentWindow->Name : "NULL"); } TreePop(); } diff --git a/core/deps/imgui/imgui.h b/core/deps/imgui/imgui.h index ecadd5077..40efb4aed 100644 --- a/core/deps/imgui/imgui.h +++ b/core/deps/imgui/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.90.6 WIP // (headers) // Help: @@ -7,15 +7,19 @@ // - Read top of imgui.cpp for more details, links and comments. // Resources: -// - FAQ https://dearimgui.com/faq -// - Getting Started https://dearimgui.com/getting-started -// - Homepage https://github.com/ocornut/imgui -// - Releases & changelog https://github.com/ocornut/imgui/releases -// - Gallery https://github.com/ocornut/imgui/issues/6897 (please post your screenshots/video there!) -// - Wiki https://github.com/ocornut/imgui/wiki (lots of good stuff there) -// - Glossary https://github.com/ocornut/imgui/wiki/Glossary -// - Issues & support https://github.com/ocornut/imgui/issues -// - Tests & Automation https://github.com/ocornut/imgui_test_engine +// - FAQ ........................ https://dearimgui.com/faq (in repository as docs/FAQ.md) +// - Homepage ................... https://github.com/ocornut/imgui +// - Releases & changelog ....... https://github.com/ocornut/imgui/releases +// - Gallery .................... https://github.com/ocornut/imgui/issues/7503 (please post your screenshots/video there!) +// - Wiki ....................... https://github.com/ocornut/imgui/wiki (lots of good stuff there) +// - Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started (how to integrate in an existing app by adding ~25 lines of code) +// - Third-party Extensions https://github.com/ocornut/imgui/wiki/Useful-Extensions (ImPlot & many more) +// - Bindings/Backends https://github.com/ocornut/imgui/wiki/Bindings (language bindings, backends for various tech/engines) +// - Glossary https://github.com/ocornut/imgui/wiki/Glossary +// - Debug Tools https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Software using Dear ImGui https://github.com/ocornut/imgui/wiki/Software-using-dear-imgui +// - Issues & support ........... https://github.com/ocornut/imgui/issues +// - Test Engine & Automation ... https://github.com/ocornut/imgui_test_engine (test suite, test engine to automate your apps) // For first-time users having issues compiling/linking/running/loading fonts: // please post in https://github.com/ocornut/imgui/discussions if you cannot find a solution in resources above. @@ -23,8 +27,8 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') -#define IMGUI_VERSION "1.90.4" -#define IMGUI_VERSION_NUM 19040 +#define IMGUI_VERSION "1.90.6" +#define IMGUI_VERSION_NUM 19060 #define IMGUI_HAS_TABLE /* @@ -126,6 +130,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe #pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind @@ -171,8 +176,9 @@ struct ImGuiViewport; // A Platform Window (always only one in 'ma // Enumerations // - We don't use strongly typed enums much because they add constraints (can't extend in private code, can't store typed in bit fields, extra casting on iteration) // - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! -// In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. enum ImGuiKey : int; // -> enum ImGuiKey // Enum: A key identifier (ImGuiKey_XXX or ImGuiMod_XXX value) enum ImGuiMouseSource : int; // -> enum ImGuiMouseSource // Enum; A mouse input source identifier (Mouse, TouchScreen, Pen) typedef int ImGuiCol; // -> enum ImGuiCol_ // Enum: A color identifier for styling @@ -187,8 +193,9 @@ typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A // Flags (declared as int to allow using as flags without overhead, and to not pollute the top of this file) // - Tip: Use your programming IDE navigation facilities on the names in the _central column_ below to find the actual flags/enum lists! -// In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas build @@ -1096,11 +1103,12 @@ enum ImGuiTreeNodeFlags_ ImGuiTreeNodeFlags_Leaf = 1 << 8, // No collapsing, no arrow (use as a convenience for leaf nodes). ImGuiTreeNodeFlags_Bullet = 1 << 9, // Display a bullet instead of arrow. IMPORTANT: node can still be marked open/close if you don't set the _Leaf flag! ImGuiTreeNodeFlags_FramePadding = 1 << 10, // Use FramePadding (even for an unframed text node) to vertically align text baseline to regular widget height. Equivalent to calling AlignTextToFramePadding(). - ImGuiTreeNodeFlags_SpanAvailWidth = 1 << 11, // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line. In the future we may refactor the hit system to be front-to-back, allowing natural overlaps and then this can become the default. - ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12, // Extend hit box to the left-most and right-most edges (bypass the indented area). - ImGuiTreeNodeFlags_SpanAllColumns = 1 << 13, // Frame will span all columns of its container table (text will still fit in current column) - ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 14, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) - //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 15, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible + ImGuiTreeNodeFlags_SpanAvailWidth = 1 << 11, // Extend hit box to the right-most edge, even if not framed. This is not the default in order to allow adding other items on the same line without using AllowOverlap mode. + ImGuiTreeNodeFlags_SpanFullWidth = 1 << 12, // Extend hit box to the left-most and right-most edges (cover the indent area). + ImGuiTreeNodeFlags_SpanTextWidth = 1 << 13, // Narrow hit box + narrow hovering highlight, will only cover the label text. + ImGuiTreeNodeFlags_SpanAllColumns = 1 << 14, // Frame will span all columns of its container table (text will still fit in current column) + ImGuiTreeNodeFlags_NavLeftJumpsBackHere = 1 << 15, // (WIP) Nav: left direction may move to this TreeNode() from any of its child (items submitted between TreeNode and TreePop) + //ImGuiTreeNodeFlags_NoScrollOnOpen = 1 << 16, // FIXME: TODO: Disable automatic scroll on TreePop() if node got just open and contents is not visible ImGuiTreeNodeFlags_CollapsingHeader = ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_NoTreePushOnOpen | ImGuiTreeNodeFlags_NoAutoOpenOnLog, #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -1544,41 +1552,45 @@ enum ImGuiCol_ // - The enum only refers to fields of ImGuiStyle which makes sense to be pushed/popped inside UI code. // During initialization or between frames, feel free to just poke into ImGuiStyle directly. // - Tip: Use your programming IDE navigation facilities on the names in the _second column_ below to find the actual members and their description. -// In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. // - When changing this enum, you need to update the associated internal table GStyleVarInfo[] accordingly. This is where we link enum values to members offset/type. enum ImGuiStyleVar_ { - // Enum name --------------------- // Member in ImGuiStyle structure (see ImGuiStyle for descriptions) - ImGuiStyleVar_Alpha, // float Alpha - ImGuiStyleVar_DisabledAlpha, // float DisabledAlpha - ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding - ImGuiStyleVar_WindowRounding, // float WindowRounding - ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize - ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize - ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign - ImGuiStyleVar_ChildRounding, // float ChildRounding - ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize - ImGuiStyleVar_PopupRounding, // float PopupRounding - ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize - ImGuiStyleVar_FramePadding, // ImVec2 FramePadding - ImGuiStyleVar_FrameRounding, // float FrameRounding - ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize - ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing - ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing - ImGuiStyleVar_IndentSpacing, // float IndentSpacing - ImGuiStyleVar_CellPadding, // ImVec2 CellPadding - ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize - ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding - ImGuiStyleVar_GrabMinSize, // float GrabMinSize - ImGuiStyleVar_GrabRounding, // float GrabRounding - ImGuiStyleVar_TabRounding, // float TabRounding - ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize - ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign - ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign - ImGuiStyleVar_SeparatorTextBorderSize,// float SeparatorTextBorderSize - ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign - ImGuiStyleVar_SeparatorTextPadding,// ImVec2 SeparatorTextPadding + // Enum name -------------------------- // Member in ImGuiStyle structure (see ImGuiStyle for descriptions) + ImGuiStyleVar_Alpha, // float Alpha + ImGuiStyleVar_DisabledAlpha, // float DisabledAlpha + ImGuiStyleVar_WindowPadding, // ImVec2 WindowPadding + ImGuiStyleVar_WindowRounding, // float WindowRounding + ImGuiStyleVar_WindowBorderSize, // float WindowBorderSize + ImGuiStyleVar_WindowMinSize, // ImVec2 WindowMinSize + ImGuiStyleVar_WindowTitleAlign, // ImVec2 WindowTitleAlign + ImGuiStyleVar_ChildRounding, // float ChildRounding + ImGuiStyleVar_ChildBorderSize, // float ChildBorderSize + ImGuiStyleVar_PopupRounding, // float PopupRounding + ImGuiStyleVar_PopupBorderSize, // float PopupBorderSize + ImGuiStyleVar_FramePadding, // ImVec2 FramePadding + ImGuiStyleVar_FrameRounding, // float FrameRounding + ImGuiStyleVar_FrameBorderSize, // float FrameBorderSize + ImGuiStyleVar_ItemSpacing, // ImVec2 ItemSpacing + ImGuiStyleVar_ItemInnerSpacing, // ImVec2 ItemInnerSpacing + ImGuiStyleVar_IndentSpacing, // float IndentSpacing + ImGuiStyleVar_CellPadding, // ImVec2 CellPadding + ImGuiStyleVar_ScrollbarSize, // float ScrollbarSize + ImGuiStyleVar_ScrollbarRounding, // float ScrollbarRounding + ImGuiStyleVar_GrabMinSize, // float GrabMinSize + ImGuiStyleVar_GrabRounding, // float GrabRounding + ImGuiStyleVar_TabRounding, // float TabRounding + ImGuiStyleVar_TabBorderSize, // float TabBorderSize + ImGuiStyleVar_TabBarBorderSize, // float TabBarBorderSize + ImGuiStyleVar_TableAngledHeadersAngle, // float TableAngledHeadersAngle + ImGuiStyleVar_TableAngledHeadersTextAlign,// ImVec2 TableAngledHeadersTextAlign + ImGuiStyleVar_ButtonTextAlign, // ImVec2 ButtonTextAlign + ImGuiStyleVar_SelectableTextAlign, // ImVec2 SelectableTextAlign + ImGuiStyleVar_SeparatorTextBorderSize, // float SeparatorTextBorderSize + ImGuiStyleVar_SeparatorTextAlign, // ImVec2 SeparatorTextAlign + ImGuiStyleVar_SeparatorTextPadding, // ImVec2 SeparatorTextPadding ImGuiStyleVar_COUNT }; @@ -1993,7 +2005,7 @@ struct ImGuiStyle float FrameBorderSize; // Thickness of border around frames. Generally set to 0.0f or 1.0f. (Other values are not well tested and more CPU/GPU costly). ImVec2 ItemSpacing; // Horizontal and vertical spacing between widgets/lines. ImVec2 ItemInnerSpacing; // Horizontal and vertical spacing between within elements of a composed widget (e.g. a slider and its label). - ImVec2 CellPadding; // Padding within a table cell. CellPadding.y may be altered between different rows. + ImVec2 CellPadding; // Padding within a table cell. Cellpadding.x is locked for entire table. CellPadding.y may be altered between different rows. ImVec2 TouchExtraPadding; // Expand reactive bounding box for touch-based system where touch position is not accurate enough. Unfortunately we don't sort widgets so priority on overlap will always be given to the first widget. So don't grow this too much! float IndentSpacing; // Horizontal indentation when e.g. entering a tree node. Generally == (FontSize + FramePadding.x*2). float ColumnsMinSpacing; // Minimum horizontal spacing between two columns. Preferably > (FramePadding.x + 1). @@ -2007,6 +2019,7 @@ struct ImGuiStyle float TabMinWidthForCloseButton; // Minimum width for close button to appear on an unselected tab when hovered. Set to 0.0f to always show when hovering, set to FLT_MAX to never show close button unless selected. float TabBarBorderSize; // Thickness of tab-bar separator, which takes on the tab active color to denote focus. float TableAngledHeadersAngle; // Angle of angled headers (supported values range from -50.0f degrees to +50.0f degrees). + ImVec2 TableAngledHeadersTextAlign;// Alignment of angled headers within the cell ImGuiDir ColorButtonPosition; // Side of the color button in the ColorEdit4 widget (left/right). Defaults to ImGuiDir_Right. ImVec2 ButtonTextAlign; // Alignment of button text when button is larger than text. Defaults to (0.5f, 0.5f) (centered). ImVec2 SelectableTextAlign; // Alignment of selectable text. Defaults to (0.0f, 0.0f) (top-left aligned). It's generally important to keep this left-aligned if you want to lay multiple items on a same line. @@ -2040,6 +2053,9 @@ struct ImGuiStyle //----------------------------------------------------------------------------- // Communicate most settings and inputs/outputs to Dear ImGui using this structure. // Access via ImGui::GetIO(). Read 'Programmer guide' section in .cpp file for general usage. +// It is generally expected that: +// - initialization: backends and user code writes to ImGuiIO. +// - main loop: backends writes to ImGuiIO, user code and imgui code reads from ImGuiIO. //----------------------------------------------------------------------------- // [Internal] Storage used by IsKeyDown(), IsKeyPressed() etc functions. @@ -2186,16 +2202,6 @@ struct ImGuiIO int MetricsActiveWindows; // Number of active windows ImVec2 MouseDelta; // Mouse delta. Note that this is zero if either current or previous position are invalid (-FLT_MAX,-FLT_MAX), so a disappearing/reappearing mouse won't have a huge delta. - // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. - // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). - // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. - bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. - float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. - //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. -#endif - //------------------------------------------------------------------ // [Internal] Dear ImGui will maintain those fields. Forward compatibility not guaranteed! //------------------------------------------------------------------ @@ -2241,6 +2247,16 @@ struct ImGuiIO ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16() ImVector InputQueueCharacters; // Queue of _characters_ input (obtained by platform backend). Fill using AddInputCharacter() helper. + // Legacy: before 1.87, we required backend to fill io.KeyMap[] (imgui->native map) during initialization and io.KeysDown[] (native indices) every frame. + // This is still temporarily supported as a legacy feature. However the new preferred scheme is for backend to call io.AddKeyEvent(). + // Old (<1.87): ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Space]) --> New (1.87+) ImGui::IsKeyPressed(ImGuiKey_Space) +#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO + int KeyMap[ImGuiKey_COUNT]; // [LEGACY] Input: map of indices into the KeysDown[512] entries array which represent your "native" keyboard state. The first 512 are now unused and should be kept zero. Legacy backend will write into KeyMap[] using ImGuiKey_ indices which are always >512. + bool KeysDown[ImGuiKey_COUNT]; // [LEGACY] Input: Keyboard keys that are pressed (ideally left in the "native" order your engine has access to keyboard keys, so you can use your own defines/enums for keys). This used to be [512] sized. It is now ImGuiKey_COUNT to allow legacy io.KeysDown[GetKeyIndex(...)] to work without an overflow. + float NavInputs[ImGuiNavInput_COUNT]; // [LEGACY] Since 1.88, NavInputs[] was removed. Backends from 1.60 to 1.86 won't build. Feed gamepad inputs via io.AddKeyEvent() and ImGuiKey_GamepadXXX enums. + //void* ImeWindowHandle; // [Obsoleted in 1.87] Set ImGuiViewport::PlatformHandleRaw instead. Set this to your HWND to get automatic IME cursor positioning. +#endif + IMGUI_API ImGuiIO(); }; @@ -2265,6 +2281,8 @@ struct ImGuiInputTextCallbackData void* UserData; // What user passed to InputText() // Read-only // Arguments for the different callback events + // - During Resize callback, Buf will be same as your input buffer. + // - However, during Completion/History/Always callback, Buf always points to our own internal data (it is not the same as your buffer)! Changes to it will be reflected into your own buffer shortly after the callback. // - To modify the text buffer in a callback, prefer using the InsertChars() / DeleteChars() function. InsertChars() will take care of calling the resize callback if necessary. // - If you know your edits are not going to resize the underlying buffer allocation, you may modify the contents of 'Buf[]' directly. You need to update 'BufTextLen' accordingly (0 <= BufTextLen < BufSize) and set 'BufDirty'' to true so InputText can update its internal state. ImWchar EventChar; // Character input // Read-write // [CharFilter] Replace character with another one, or set to zero to drop. return 1 is equivalent to setting EventChar=0; @@ -2708,15 +2726,15 @@ struct ImDrawList // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. ImDrawListSharedData* _Data; // Pointer to shared draw data (you can use ImGui::GetDrawListSharedData() to get the one from current ImGui context) - const char* _OwnerName; // Pointer to owner window's name for debugging ImDrawVert* _VtxWritePtr; // [Internal] point within VtxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) ImDrawIdx* _IdxWritePtr; // [Internal] point within IdxBuffer.Data after each add command (to avoid using the ImVector<> operators too much) - ImVector _ClipRectStack; // [Internal] - ImVector _TextureIdStack; // [Internal] ImVector _Path; // [Internal] current path building ImDrawCmdHeader _CmdHeader; // [Internal] template of active commands. Fields should match those of CmdBuffer.back(). ImDrawListSplitter _Splitter; // [Internal] for channels api (note: prefer using your own persistent instance of ImDrawListSplitter!) + ImVector _ClipRectStack; // [Internal] + ImVector _TextureIdStack; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content + const char* _OwnerName; // Pointer to owner window's name for debugging // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData() or create and use your own ImDrawListSharedData (so you can use ImDrawList without ImGui) ImDrawList(ImDrawListSharedData* shared_data) { memset(this, 0, sizeof(*this)); _Data = shared_data; } @@ -2749,15 +2767,20 @@ struct ImDrawList IMGUI_API void AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments = 0); IMGUI_API void AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness = 1.0f); IMGUI_API void AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments); - IMGUI_API void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); - IMGUI_API void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0); + IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); - IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); - IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) + // General polygon + // - Only simple polygons are supported by filling functions (no self-intersections, no holes). + // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience fo user but not used by main library. + IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags, float thickness); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); + IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); + // Image primitives // - Read FAQ to understand what ImTextureID is. // - "p_min" and "p_max" represent the upper-left and lower-right corners of the rectangle. @@ -2773,10 +2796,11 @@ struct ImDrawList inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } + inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } inline void PathStroke(ImU32 col, ImDrawFlags flags = 0, float thickness = 1.0f) { AddPolyline(_Path.Data, _Path.Size, col, flags, thickness); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle - IMGUI_API void PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse + IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse IMGUI_API void PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0); // Cubic Bezier (4 control points) IMGUI_API void PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, int num_segments = 0); // Quadratic Bezier (3 control points) IMGUI_API void PathRect(const ImVec2& rect_min, const ImVec2& rect_max, float rounding = 0.0f, ImDrawFlags flags = 0); @@ -2809,6 +2833,9 @@ struct ImDrawList inline void PrimVtx(const ImVec2& pos, const ImVec2& uv, ImU32 col) { PrimWriteIdx((ImDrawIdx)_VtxCurrentIdx); PrimWriteVtx(pos, uv, col); } // Write vertex with unique index // Obsolete names + //inline void AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f) { AddEllipse(center, ImVec2(radius_x, radius_y), col, rot, num_segments, thickness); } // OBSOLETED in 1.90.5 (Mar 2024) + //inline void AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot = 0.0f, int num_segments = 0) { AddEllipseFilled(center, ImVec2(radius_x, radius_y), col, rot, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) + //inline void PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments = 0) { PathEllipticalArcTo(center, ImVec2(radius_x, radius_y), rot, a_min, a_max, num_segments); } // OBSOLETED in 1.90.5 (Mar 2024) //inline void AddBezierCurve(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0) { AddBezierCubic(p1, p2, p3, p4, col, thickness, num_segments); } // OBSOLETED in 1.80 (Jan 2021) //inline void PathBezierCurveTo(const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, int num_segments = 0) { PathBezierCubicCurveTo(p2, p3, p4, num_segments); } // OBSOLETED in 1.80 (Jan 2021) @@ -3163,15 +3190,6 @@ struct ImGuiPlatformImeData // Please keep your copy of dear imgui up to date! Occasionally set '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' in imconfig.h to stay ahead. //----------------------------------------------------------------------------- -namespace ImGui -{ -#ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // map ImGuiKey_* values into legacy native key index. == io.KeyMap[key] -#else - static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END && "ImGuiKey and native_index was merged together and native_index is disabled by IMGUI_DISABLE_OBSOLETE_KEYIO. Please switch to ImGuiKey."); return key; } -#endif -} - #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS namespace ImGui { @@ -3193,6 +3211,9 @@ namespace ImGui // OBSOLETED in 1.88 (from May 2022) static inline void CaptureKeyboardFromApp(bool want_capture_keyboard = true) { SetNextFrameWantCaptureKeyboard(want_capture_keyboard); } // Renamed as name was misleading + removed default value. static inline void CaptureMouseFromApp(bool want_capture_mouse = true) { SetNextFrameWantCaptureMouse(want_capture_mouse); } // Renamed as name was misleading + removed default value. + // OBSOLETED in 1.87 (from February 2022) + IMGUI_API ImGuiKey GetKeyIndex(ImGuiKey key); // Map ImGuiKey_* values into legacy native key index. == io.KeyMap[key]. When using a 1.87+ backend using io.AddKeyEvent(), calling GetKeyIndex() with ANY ImGuiKey_XXXX values will return the same value! + //static inline ImGuiKey GetKeyIndex(ImGuiKey key) { IM_ASSERT(key >= ImGuiKey_NamedKey_BEGIN && key < ImGuiKey_NamedKey_END); return key; } // Some of the older obsolete names along with their replacement (commented out so they are not reported in IDE) //-- OBSOLETED in 1.86 (from November 2021) diff --git a/core/deps/imgui/imgui_demo.cpp b/core/deps/imgui/imgui_demo.cpp index 707c14d38..0130cdb41 100644 --- a/core/deps/imgui/imgui_demo.cpp +++ b/core/deps/imgui/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.90.6 // (demo code) // Help: @@ -7,9 +7,14 @@ // - Need help integrating Dear ImGui in your codebase? // - Read Getting Started https://github.com/ocornut/imgui/wiki/Getting-Started // - Read 'Programmer guide' in imgui.cpp for notes on how to setup Dear ImGui in your codebase. -// Read imgui.cpp for more details, documentation and comments. +// Read top of imgui.cpp and imgui.h for many details, documentation, comments, links. // Get the latest version at https://github.com/ocornut/imgui +// How to easily locate code? +// - Use the Item Picker to debug break in code by clicking any widgets: https://github.com/ocornut/imgui/wiki/Debug-Tools +// - Browse an online version the demo with code linked to hovered widgets: https://pthom.github.io/imgui_manual_online/manual/imgui_manual.html +// - Find a visible string and search for it in the code! + //--------------------------------------------------- // PLEASE DO NOT REMOVE THIS FILE FROM YOUR PROJECT! //--------------------------------------------------- @@ -54,8 +59,9 @@ // Because we can't assume anything about your support of maths operators, we cannot use them in imgui_demo.cpp. // Navigating this file: -// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. /* @@ -130,6 +136,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wreserved-id-macro" // warning: macro name is a reserved identifier #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" // warning: cast to pointer from integer of different size @@ -899,13 +906,18 @@ static void ShowDemoWindowWidgets() if (i == 0) ImGui::SetNextItemOpen(true, ImGuiCond_Once); - if (ImGui::TreeNode((void*)(intptr_t)i, "Child %d", i)) + // Here we use PushID() to generate a unique base ID, and then the "" used as TreeNode id won't conflict. + // An alternative to using 'PushID() + TreeNode("", ...)' to generate a unique ID is to use 'TreeNode((void*)(intptr_t)i, ...)', + // aka generate a dummy pointer-sized value to be hashed. The demo below uses that technique. Both are fine. + ImGui::PushID(i); + if (ImGui::TreeNode("", "Child %d", i)) { ImGui::Text("blah blah"); ImGui::SameLine(); if (ImGui::SmallButton("button")) {} ImGui::TreePop(); } + ImGui::PopID(); } ImGui::TreePop(); } @@ -923,7 +935,10 @@ static void ShowDemoWindowWidgets() ImGui::CheckboxFlags("ImGuiTreeNodeFlags_OpenOnDoubleClick", &base_flags, ImGuiTreeNodeFlags_OpenOnDoubleClick); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAvailWidth", &base_flags, ImGuiTreeNodeFlags_SpanAvailWidth); ImGui::SameLine(); HelpMarker("Extend hit area to all available width instead of allowing more items to be laid out after the node."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &base_flags, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanTextWidth", &base_flags, ImGuiTreeNodeFlags_SpanTextWidth); ImGui::SameLine(); HelpMarker("Reduce hit area to the text label and a bit of margin."); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &base_flags, ImGuiTreeNodeFlags_SpanAllColumns); ImGui::SameLine(); HelpMarker("For use in Tables only."); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_AllowOverlap", &base_flags, ImGuiTreeNodeFlags_AllowOverlap); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_Framed", &base_flags, ImGuiTreeNodeFlags_Framed); ImGui::SameLine(); HelpMarker("Draw frame with background (e.g. for CollapsingHeader)"); ImGui::Checkbox("Align label with current X position", &align_label_with_current_x_position); ImGui::Checkbox("Test tree node as drag source", &test_drag_and_drop); ImGui::Text("Hello!"); @@ -956,6 +971,12 @@ static void ShowDemoWindowWidgets() ImGui::Text("This is a drag and drop source"); ImGui::EndDragDropSource(); } + if (i == 2) + { + // Item 2 has an additional inline button to help demonstrate SpanTextWidth. + ImGui::SameLine(); + if (ImGui::SmallButton("button")) {} + } if (node_open) { ImGui::BulletText("Blah blah\nBlah Blah"); @@ -1288,6 +1309,7 @@ static void ShowDemoWindowWidgets() } ImGui::EndListBox(); } + ImGui::SameLine(); HelpMarker("Here we are sharing selection state between both boxes."); // Custom size: use all width, 5 items tall ImGui::Text("Full-width:"); @@ -1818,10 +1840,10 @@ static void ShowDemoWindowWidgets() ImGui::Checkbox("Animate", &animate); // Plot as lines and plot as histogram - IMGUI_DEMO_MARKER("Widgets/Plotting/PlotLines, PlotHistogram"); static float arr[] = { 0.6f, 0.1f, 1.0f, 0.5f, 0.92f, 0.1f, 0.2f }; ImGui::PlotLines("Frame Times", arr, IM_ARRAYSIZE(arr)); ImGui::PlotHistogram("Histogram", arr, IM_ARRAYSIZE(arr), 0, NULL, 0.0f, 1.0f, ImVec2(0, 80.0f)); + //ImGui::SameLine(); HelpMarker("Consider using ImPlot instead!"); // Fill an array of contiguous float values to plot // Tip: If your float aren't contiguous but part of a structure, you can pass a pointer to your first float @@ -1871,15 +1893,17 @@ static void ShowDemoWindowWidgets() ImGui::PlotHistogram("Histogram", func, NULL, display_count, 0, NULL, -1.0f, 1.0f, ImVec2(0, 80)); ImGui::Separator(); + ImGui::TreePop(); + } + + IMGUI_DEMO_MARKER("Widgets/Progress Bars"); + if (ImGui::TreeNode("Progress Bars")) + { // Animate a simple progress bar - IMGUI_DEMO_MARKER("Widgets/Plotting/ProgressBar"); static float progress = 0.0f, progress_dir = 1.0f; - if (animate) - { - progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; - if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } - if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } - } + progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime; + if (progress >= +1.1f) { progress = +1.1f; progress_dir *= -1.0f; } + if (progress <= -0.1f) { progress = -0.1f; progress_dir *= -1.0f; } // Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width, // or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth. @@ -1891,6 +1915,13 @@ static void ShowDemoWindowWidgets() char buf[32]; sprintf(buf, "%d/%d", (int)(progress_saturated * 1753), 1753); ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf); + + // Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value. + // Adjust the factor if you want to adjust the animation speed. + ImGui::ProgressBar(-1.0f * (float)ImGui::GetTime(), ImVec2(0.0f, 0.0f), "Searching.."); + ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); + ImGui::Text("Indeterminate"); + ImGui::TreePop(); } @@ -2481,9 +2512,7 @@ static void ShowDemoWindowWidgets() { IM_UNUSED(payload); ImGui::SetMouseCursor(ImGuiMouseCursor_NotAllowed); - ImGui::BeginTooltip(); - ImGui::Text("Cannot drop here!"); - ImGui::EndTooltip(); + ImGui::SetTooltip("Cannot drop here!"); } ImGui::EndDragDropTarget(); } @@ -5139,7 +5168,8 @@ static void ShowDemoWindowTables() static ImGuiTableFlags flags = ImGuiTableFlags_BordersV | ImGuiTableFlags_BordersOuterH | ImGuiTableFlags_Resizable | ImGuiTableFlags_RowBg | ImGuiTableFlags_NoBordersInBody; static ImGuiTreeNodeFlags tree_node_flags = ImGuiTreeNodeFlags_SpanAllColumns; - ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &tree_node_flags, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanFullWidth", &tree_node_flags, ImGuiTreeNodeFlags_SpanFullWidth); + ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanTextWidth", &tree_node_flags, ImGuiTreeNodeFlags_SpanTextWidth); ImGui::CheckboxFlags("ImGuiTreeNodeFlags_SpanAllColumns", &tree_node_flags, ImGuiTreeNodeFlags_SpanAllColumns); HelpMarker("See \"Columns flags\" section to configure how indentation is applied to individual columns."); @@ -5328,6 +5358,17 @@ static void ShowDemoWindowTables() ImGui::SliderInt("Frozen rows", &frozen_rows, 0, 2); ImGui::CheckboxFlags("Disable header contributing to column width", &column_flags, ImGuiTableColumnFlags_NoHeaderWidth); + if (ImGui::TreeNode("Style settings")) + { + ImGui::SameLine(); + HelpMarker("Giving access to some ImGuiStyle value in this demo for convenience."); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::SliderAngle("style.TableAngledHeadersAngle", &ImGui::GetStyle().TableAngledHeadersAngle, -50.0f, +50.0f); + ImGui::SetNextItemWidth(ImGui::GetFontSize() * 8); + ImGui::SliderFloat2("style.TableAngledHeadersTextAlign", (float*)&ImGui::GetStyle().TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); + ImGui::TreePop(); + } + if (ImGui::BeginTable("table_angled_headers", columns_count, table_flags, ImVec2(0.0f, TEXT_BASE_HEIGHT * 12))) { ImGui::TableSetupColumn(column_names[0], ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoReorder); @@ -5479,6 +5520,7 @@ static void ShowDemoWindowTables() HelpMarker("Multiple tables with the same identifier will share their settings, width, visibility, order etc."); static ImGuiTableFlags flags = ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_NoSavedSettings; + ImGui::CheckboxFlags("ImGuiTableFlags_Resizable", &flags, ImGuiTableFlags_Resizable); ImGui::CheckboxFlags("ImGuiTableFlags_ScrollY", &flags, ImGuiTableFlags_ScrollY); ImGui::CheckboxFlags("ImGuiTableFlags_SizingFixedFit", &flags, ImGuiTableFlags_SizingFixedFit); ImGui::CheckboxFlags("ImGuiTableFlags_HighlightHoveredColumn", &flags, ImGuiTableFlags_HighlightHoveredColumn); @@ -6351,7 +6393,7 @@ void ImGui::ShowAboutWindow(bool* p_open) ImGui::Separator(); ImGui::Text("By Omar Cornut and all Dear ImGui contributors."); ImGui::Text("Dear ImGui is licensed under the MIT License, see LICENSE for more information."); - ImGui::Text("If your company uses this, please consider sponsoring the project!"); + ImGui::Text("If your company uses this, please consider funding the project."); static bool show_config_info = false; ImGui::Checkbox("Config/Build Information", &show_config_info); @@ -6616,6 +6658,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SeparatorText("Tables"); ImGui::SliderFloat2("CellPadding", (float*)&style.CellPadding, 0.0f, 20.0f, "%.0f"); ImGui::SliderAngle("TableAngledHeadersAngle", &style.TableAngledHeadersAngle, -50.0f, +50.0f); + ImGui::SliderFloat2("TableAngledHeadersTextAlign", (float*)&style.TableAngledHeadersTextAlign, 0.0f, 1.0f, "%.2f"); ImGui::SeparatorText("Widgets"); ImGui::SliderFloat2("WindowTitleAlign", (float*)&style.WindowTitleAlign, 0.0f, 1.0f, "%.2f"); @@ -7789,7 +7832,7 @@ static void ShowExampleAppConstrainedResize(bool* p_open) if (type == 2) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 0), ImVec2(-1, FLT_MAX)); // Resize vertical + lock current width if (type == 3) ImGui::SetNextWindowSizeConstraints(ImVec2(0, -1), ImVec2(FLT_MAX, -1)); // Resize horizontal + lock current height if (type == 4) ImGui::SetNextWindowSizeConstraints(ImVec2(400, -1), ImVec2(500, -1)); // Width Between and 400 and 500 - if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 500), ImVec2(-1, FLT_MAX)); // Height at least 400 + if (type == 5) ImGui::SetNextWindowSizeConstraints(ImVec2(-1, 400), ImVec2(-1, FLT_MAX)); // Height at least 400 if (type == 6) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::AspectRatio, (void*)&aspect_ratio); // Aspect ratio if (type == 7) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Square); // Always Square if (type == 8) ImGui::SetNextWindowSizeConstraints(ImVec2(0, 0), ImVec2(FLT_MAX, FLT_MAX), CustomConstraints::Step, (void*)&fixed_step); // Fixed Step @@ -7964,6 +8007,14 @@ static void ShowExampleAppWindowTitles(bool*) // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() //----------------------------------------------------------------------------- +// Add a |_| looking shape +static void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz) +{ + const ImVec2 pos_norms[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; + for (const ImVec2& p : pos_norms) + draw_list->PathLineTo(ImVec2(x + 0.5f + (int)(sz * p.x), y + 0.5f + (int)(sz * p.y))); +} + // Demonstrate using the low-level ImDrawList to draw custom shapes. static void ShowExampleAppCustomRendering(bool* p_open) { @@ -8047,12 +8098,14 @@ static void ShowExampleAppCustomRendering(bool* p_open) float th = (n == 0) ? 1.0f : thickness; draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle - draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, sz*0.3f, col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse + draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, ImDrawFlags_None, th); x += sz + spacing; // Square draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, ImDrawFlags_None, th); x += sz + spacing; // Square with all rounded corners draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, corners_tl_br, th); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle + PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, ImDrawFlags_Closed, th); x += sz + spacing; // Concave Shape + //draw_list->AddPolyline(concave_shape, IM_ARRAYSIZE(concave_shape), col, ImDrawFlags_Closed, th); draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line @@ -8076,12 +8129,13 @@ static void ShowExampleAppCustomRendering(bool* p_open) // Filled shapes draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngon_sides); x += sz + spacing; // N-gon draw_list->AddCircleFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circle_segments); x += sz + spacing; // Circle - draw_list->AddEllipseFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, sz * 0.3f, col, -0.3f, circle_segments); x += sz + spacing;// Ellipse + draw_list->AddEllipseFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), ImVec2(sz * 0.5f, sz * 0.3f), col, -0.3f, circle_segments); x += sz + spacing;// Ellipse draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col); x += sz + spacing; // Square draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f); x += sz + spacing; // Square with all rounded corners draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col); x += sz + spacing; // Triangle //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle + PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) @@ -8097,15 +8151,10 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->PathFillConvex(col); x += sz + spacing; - // Cubic Bezier Curve (4 control points): this is concave so not drawing it yet - //draw_list->PathLineTo(ImVec2(x + cp4[0].x, y + cp4[0].y)); - //draw_list->PathBezierCubicCurveTo(ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), curve_segments); - //draw_list->PathFillConvex(col); - //x += sz + spacing; - draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); + x += sz + spacing; - ImGui::Dummy(ImVec2((sz + spacing) * 12.2f, (sz + spacing) * 3.0f)); + ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f)); ImGui::PopItemWidth(); ImGui::EndTabItem(); } diff --git a/core/deps/imgui/imgui_draw.cpp b/core/deps/imgui/imgui_draw.cpp index 1319a6e1d..04aba119e 100644 --- a/core/deps/imgui/imgui_draw.cpp +++ b/core/deps/imgui/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.90.6 // (drawing and font code) /* @@ -8,6 +8,7 @@ Index of this file: // [SECTION] STB libraries implementation // [SECTION] Style functions // [SECTION] ImDrawList +// [SECTION] ImTriangulator, ImDrawList concave polygon fill // [SECTION] ImDrawListSplitter // [SECTION] ImDrawData // [SECTION] Helpers ShadeVertsXXX functions @@ -64,6 +65,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wdouble-promotion" // warning: implicit conversion from 'float' to 'double' when passing argument to function // using printf() is a misery with this as C++ va_arg ellipsis changes float to double. #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wreserved-identifier" // warning: identifier '_Xxx' is reserved because it starts with '_' followed by a capital letter +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind #pragma GCC diagnostic ignored "-Wunused-function" // warning: 'xxxx' defined but not used @@ -383,6 +385,7 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error) } // Initialize before use in a new frame. We always have a command ready in the buffer. +// In the majority of cases, you would want to call PushClipRect() and PushTextureID() after this. void ImDrawList::_ResetForNewFrame() { // Verify that the ImDrawCmd fields we want to memcmp() are contiguous in memory. @@ -1217,10 +1220,10 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa } } -void ImDrawList::PathEllipticalArcTo(const ImVec2& center, float radius_x, float radius_y, float rot, float a_min, float a_max, int num_segments) +void ImDrawList::PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments) { if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius_x, radius_y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. _Path.reserve(_Path.Size + (num_segments + 1)); @@ -1229,11 +1232,10 @@ void ImDrawList::PathEllipticalArcTo(const ImVec2& center, float radius_x, float for (int i = 0; i <= num_segments; i++) { const float a = a_min + ((float)i / (float)num_segments) * (a_max - a_min); - ImVec2 point(ImCos(a) * radius_x, ImSin(a) * radius_y); - const float rel_x = (point.x * cos_rot) - (point.y * sin_rot); - const float rel_y = (point.x * sin_rot) + (point.y * cos_rot); - point.x = rel_x + center.x; - point.y = rel_y + center.y; + ImVec2 point(ImCos(a) * radius.x, ImSin(a) * radius.y); + const ImVec2 rel((point.x * cos_rot) - (point.y * sin_rot), (point.x * sin_rot) + (point.y * cos_rot)); + point.x = rel.x + center.x; + point.y = rel.y + center.y; _Path.push_back(point); } } @@ -1558,31 +1560,31 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in } // Ellipse -void ImDrawList::AddEllipse(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot, int num_segments, float thickness) +void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius_x, radius_y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathEllipticalArcTo(center, radius_x, radius_y, rot, 0.0f, a_max, num_segments - 1); + PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); PathStroke(col, true, thickness); } -void ImDrawList::AddEllipseFilled(const ImVec2& center, float radius_x, float radius_y, ImU32 col, float rot, int num_segments) +void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) { if ((col & IM_COL32_A_MASK) == 0) return; if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius_x, radius_y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathEllipticalArcTo(center, radius_x, radius_y, rot, 0.0f, a_max, num_segments - 1); + PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); PathFillConvex(col); } @@ -1613,10 +1615,11 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, if ((col & IM_COL32_A_MASK) == 0) return; + // Accept null ranges + if (text_begin == text_end || text_begin[0] == 0) + return; if (text_end == NULL) text_end = text_begin + strlen(text_begin); - if (text_begin == text_end) - return; // Pull default font/size from the shared ImDrawListSharedData instance if (font == NULL) @@ -1700,6 +1703,316 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi PopTextureID(); } +//----------------------------------------------------------------------------- +// [SECTION] ImTriangulator, ImDrawList concave polygon fill +//----------------------------------------------------------------------------- +// Triangulate concave polygons. Based on "Triangulation by Ear Clipping" paper, O(N^2) complexity. +// Reference: https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf +// Provided as a convenience for user but not used by main library. +//----------------------------------------------------------------------------- +// - ImTriangulator [Internal] +// - AddConcavePolyFilled() +//----------------------------------------------------------------------------- + +enum ImTriangulatorNodeType +{ + ImTriangulatorNodeType_Convex, + ImTriangulatorNodeType_Ear, + ImTriangulatorNodeType_Reflex +}; + +struct ImTriangulatorNode +{ + ImTriangulatorNodeType Type; + int Index; + ImVec2 Pos; + ImTriangulatorNode* Next; + ImTriangulatorNode* Prev; + + void Unlink() { Next->Prev = Prev; Prev->Next = Next; } +}; + +struct ImTriangulatorNodeSpan +{ + ImTriangulatorNode** Data = NULL; + int Size = 0; + + void push_back(ImTriangulatorNode* node) { Data[Size++] = node; } + void find_erase_unsorted(int idx) { for (int i = Size - 1; i >= 0; i--) if (Data[i]->Index == idx) { Data[i] = Data[Size - 1]; Size--; return; } } +}; + +struct ImTriangulator +{ + static int EstimateTriangleCount(int points_count) { return (points_count < 3) ? 0 : points_count - 2; } + static int EstimateScratchBufferSize(int points_count) { return sizeof(ImTriangulatorNode) * points_count + sizeof(ImTriangulatorNode*) * points_count * 2; } + + void Init(const ImVec2* points, int points_count, void* scratch_buffer); + void GetNextTriangle(unsigned int out_triangle[3]); // Return relative indexes for next triangle + + // Internal functions + void BuildNodes(const ImVec2* points, int points_count); + void BuildReflexes(); + void BuildEars(); + void FlipNodeList(); + bool IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const; + void ReclassifyNode(ImTriangulatorNode* node); + + // Internal members + int _TrianglesLeft = 0; + ImTriangulatorNode* _Nodes = NULL; + ImTriangulatorNodeSpan _Ears; + ImTriangulatorNodeSpan _Reflexes; +}; + +// Distribute storage for nodes, ears and reflexes. +// FIXME-OPT: if everything is convex, we could report it to caller and let it switch to an convex renderer +// (this would require first building reflexes to bail to convex if empty, without even building nodes) +void ImTriangulator::Init(const ImVec2* points, int points_count, void* scratch_buffer) +{ + IM_ASSERT(scratch_buffer != NULL && points_count >= 3); + _TrianglesLeft = EstimateTriangleCount(points_count); + _Nodes = (ImTriangulatorNode*)scratch_buffer; // points_count x Node + _Ears.Data = (ImTriangulatorNode**)(_Nodes + points_count); // points_count x Node* + _Reflexes.Data = (ImTriangulatorNode**)(_Nodes + points_count) + points_count; // points_count x Node* + BuildNodes(points, points_count); + BuildReflexes(); + BuildEars(); +} + +void ImTriangulator::BuildNodes(const ImVec2* points, int points_count) +{ + for (int i = 0; i < points_count; i++) + { + _Nodes[i].Type = ImTriangulatorNodeType_Convex; + _Nodes[i].Index = i; + _Nodes[i].Pos = points[i]; + _Nodes[i].Next = _Nodes + i + 1; + _Nodes[i].Prev = _Nodes + i - 1; + } + _Nodes[0].Prev = _Nodes + points_count - 1; + _Nodes[points_count - 1].Next = _Nodes; +} + +void ImTriangulator::BuildReflexes() +{ + ImTriangulatorNode* n1 = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next) + { + if (ImTriangleIsClockwise(n1->Prev->Pos, n1->Pos, n1->Next->Pos)) + continue; + n1->Type = ImTriangulatorNodeType_Reflex; + _Reflexes.push_back(n1); + } +} + +void ImTriangulator::BuildEars() +{ + ImTriangulatorNode* n1 = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, n1 = n1->Next) + { + if (n1->Type != ImTriangulatorNodeType_Convex) + continue; + if (!IsEar(n1->Prev->Index, n1->Index, n1->Next->Index, n1->Prev->Pos, n1->Pos, n1->Next->Pos)) + continue; + n1->Type = ImTriangulatorNodeType_Ear; + _Ears.push_back(n1); + } +} + +void ImTriangulator::GetNextTriangle(unsigned int out_triangle[3]) +{ + if (_Ears.Size == 0) + { + FlipNodeList(); + + ImTriangulatorNode* node = _Nodes; + for (int i = _TrianglesLeft; i >= 0; i--, node = node->Next) + node->Type = ImTriangulatorNodeType_Convex; + _Reflexes.Size = 0; + BuildReflexes(); + BuildEars(); + + // If we still don't have ears, it means geometry is degenerated. + if (_Ears.Size == 0) + { + // Return first triangle available, mimicking the behavior of convex fill. + IM_ASSERT(_TrianglesLeft > 0); // Geometry is degenerated + _Ears.Data[0] = _Nodes; + _Ears.Size = 1; + } + } + + ImTriangulatorNode* ear = _Ears.Data[--_Ears.Size]; + out_triangle[0] = ear->Prev->Index; + out_triangle[1] = ear->Index; + out_triangle[2] = ear->Next->Index; + + ear->Unlink(); + if (ear == _Nodes) + _Nodes = ear->Next; + + ReclassifyNode(ear->Prev); + ReclassifyNode(ear->Next); + _TrianglesLeft--; +} + +void ImTriangulator::FlipNodeList() +{ + ImTriangulatorNode* prev = _Nodes; + ImTriangulatorNode* temp = _Nodes; + ImTriangulatorNode* current = _Nodes->Next; + prev->Next = prev; + prev->Prev = prev; + while (current != _Nodes) + { + temp = current->Next; + + current->Next = prev; + prev->Prev = current; + _Nodes->Next = current; + current->Prev = _Nodes; + + prev = current; + current = temp; + } + _Nodes = prev; +} + +// A triangle is an ear is no other vertex is inside it. We can test reflexes vertices only (see reference algorithm) +bool ImTriangulator::IsEar(int i0, int i1, int i2, const ImVec2& v0, const ImVec2& v1, const ImVec2& v2) const +{ + ImTriangulatorNode** p_end = _Reflexes.Data + _Reflexes.Size; + for (ImTriangulatorNode** p = _Reflexes.Data; p < p_end; p++) + { + ImTriangulatorNode* reflex = *p; + if (reflex->Index != i0 && reflex->Index != i1 && reflex->Index != i2) + if (ImTriangleContainsPoint(v0, v1, v2, reflex->Pos)) + return false; + } + return true; +} + +void ImTriangulator::ReclassifyNode(ImTriangulatorNode* n1) +{ + // Classify node + ImTriangulatorNodeType type; + const ImTriangulatorNode* n0 = n1->Prev; + const ImTriangulatorNode* n2 = n1->Next; + if (!ImTriangleIsClockwise(n0->Pos, n1->Pos, n2->Pos)) + type = ImTriangulatorNodeType_Reflex; + else if (IsEar(n0->Index, n1->Index, n2->Index, n0->Pos, n1->Pos, n2->Pos)) + type = ImTriangulatorNodeType_Ear; + else + type = ImTriangulatorNodeType_Convex; + + // Update lists when a type changes + if (type == n1->Type) + return; + if (n1->Type == ImTriangulatorNodeType_Reflex) + _Reflexes.find_erase_unsorted(n1->Index); + else if (n1->Type == ImTriangulatorNodeType_Ear) + _Ears.find_erase_unsorted(n1->Index); + if (type == ImTriangulatorNodeType_Reflex) + _Reflexes.push_back(n1); + else if (type == ImTriangulatorNodeType_Ear) + _Ears.push_back(n1); + n1->Type = type; +} + +// Use ear-clipping algorithm to triangulate a simple polygon (no self-interaction, no holes). +// (Reminder: we don't perform any coarse clipping/culling in ImDrawList layer! +// It is up to caller to ensure not making costly calls that will be outside of visible area. +// As concave fill is noticeably more expensive than other primitives, be mindful of this... +// Caller can build AABB of points, and avoid filling if 'draw_list->_CmdHeader.ClipRect.Overlays(points_bb) == false') +void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_count, ImU32 col) +{ + if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) + return; + + const ImVec2 uv = _Data->TexUvWhitePixel; + ImTriangulator triangulator; + unsigned int triangle[3]; + if (Flags & ImDrawListFlags_AntiAliasedFill) + { + // Anti-aliased Fill + const float AA_SIZE = _FringeScale; + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + const int idx_count = (points_count - 2) * 3 + points_count * 6; + const int vtx_count = (points_count * 2); + PrimReserve(idx_count, vtx_count); + + // Add indexes for fill + unsigned int vtx_inner_idx = _VtxCurrentIdx; + unsigned int vtx_outer_idx = _VtxCurrentIdx + 1; + + _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); + triangulator.Init(points, points_count, _Data->TempBuffer.Data); + while (triangulator._TrianglesLeft > 0) + { + triangulator.GetNextTriangle(triangle); + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (triangle[0] << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (triangle[1] << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (triangle[2] << 1)); + _IdxWritePtr += 3; + } + + // Compute normals + _Data->TempBuffer.reserve_discard(points_count); + ImVec2* temp_normals = _Data->TempBuffer.Data; + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + float dx = p1.x - p0.x; + float dy = p1.y - p0.y; + IM_NORMALIZE2F_OVER_ZERO(dx, dy); + temp_normals[i0].x = dy; + temp_normals[i0].y = -dx; + } + + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + float dm_x = (n0.x + n1.x) * 0.5f; + float dm_y = (n0.y + n1.y) * 0.5f; + IM_FIXNORMAL2F(dm_x, dm_y); + dm_x *= AA_SIZE * 0.5f; + dm_y *= AA_SIZE * 0.5f; + + // Add vertices + _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner + _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer + _VtxWritePtr += 2; + + // Add indexes for fringes + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); + _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); + _IdxWritePtr += 6; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } + else + { + // Non Anti-aliased Fill + const int idx_count = (points_count - 2) * 3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + for (int i = 0; i < vtx_count; i++) + { + _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; + _VtxWritePtr++; + } + _Data->TempBuffer.reserve_discard((ImTriangulator::EstimateScratchBufferSize(points_count) + sizeof(ImVec2)) / sizeof(ImVec2)); + triangulator.Init(points, points_count, _Data->TempBuffer.Data); + while (triangulator._TrianglesLeft > 0) + { + triangulator.GetNextTriangle(triangle); + _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx + triangle[0]); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + triangle[1]); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + triangle[2]); + _IdxWritePtr += 3; + } + _VtxCurrentIdx += (ImDrawIdx)vtx_count; + } +} //----------------------------------------------------------------------------- // [SECTION] ImDrawListSplitter @@ -2672,8 +2985,8 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) int unscaled_ascent, unscaled_descent, unscaled_line_gap; stbtt_GetFontVMetrics(&src_tmp.FontInfo, &unscaled_ascent, &unscaled_descent, &unscaled_line_gap); - const float ascent = ImTrunc(unscaled_ascent * font_scale + ((unscaled_ascent > 0.0f) ? +1 : -1)); - const float descent = ImTrunc(unscaled_descent * font_scale + ((unscaled_descent > 0.0f) ? +1 : -1)); + const float ascent = ImCeil(unscaled_ascent * font_scale); + const float descent = ImFloor(unscaled_descent * font_scale); ImFontAtlasBuildSetupFont(atlas, dst_font, &cfg, ascent, descent); const float font_off_x = cfg.GlyphOffset.x; const float font_off_y = cfg.GlyphOffset.y + IM_ROUND(dst_font->Ascent); @@ -3768,6 +4081,8 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { x = start_x; y += line_height; + if (y > clip_rect.w) + break; // break out of main loop word_wrap_eol = NULL; s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks continue; diff --git a/core/deps/imgui/imgui_internal.h b/core/deps/imgui/imgui_internal.h index eee791d04..905b8195a 100644 --- a/core/deps/imgui/imgui_internal.h +++ b/core/deps/imgui/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.90.6 // (internal structures/api) // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. @@ -87,6 +87,8 @@ Index of this file: #pragma clang diagnostic ignored "-Wdouble-promotion" #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wmissing-noreturn" // warning: function 'xxx' could be declared with attribute 'noreturn' +#pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind @@ -124,7 +126,7 @@ struct ImDrawListSharedData; // Data shared between all ImDrawList instan struct ImGuiColorMod; // Stacked color modifier, backup of modified data so we can restore it struct ImGuiContext; // Main Dear ImGui context struct ImGuiContextHook; // Hook for extensions like ImGuiTestEngine -struct ImGuiDataVarInfo; // Variable information (e.g. to avoid style variables from an enum) +struct ImGuiDataVarInfo; // Variable information (e.g. to access style variables from an enum) struct ImGuiDataTypeInfo; // Type information associated to a ImGuiDataType enum struct ImGuiGroupData; // Stacked storage data for BeginGroup()/EndGroup() struct ImGuiInputTextState; // Internal state of the currently focused/edited text input box @@ -146,6 +148,7 @@ struct ImGuiStyleMod; // Stacked style modifier, backup of modifie struct ImGuiTabBar; // Storage for a tab bar struct ImGuiTabItem; // Storage for a tab item (within a tab bar) struct ImGuiTable; // Storage for a table +struct ImGuiTableHeaderData; // Storage for TableAngledHeadersRow() struct ImGuiTableColumn; // Storage for one column of a table struct ImGuiTableInstanceData; // Storage for one instance of a same table struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables. @@ -179,6 +182,7 @@ typedef int ImGuiSeparatorFlags; // -> enum ImGuiSeparatorFlags_ // F typedef int ImGuiTextFlags; // -> enum ImGuiTextFlags_ // Flags: for TextEx() typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // Flags: for BeginTooltipEx() typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest() +typedef int ImGuiWindowRefreshFlags; // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy() typedef void (*ImGuiErrorLogCallback)(void* user_data, const char* fmt, ...); @@ -404,6 +408,7 @@ IMGUI_API int ImTextCountCharsFromUtf8(const char* in_text, const char IMGUI_API int ImTextCountUtf8BytesFromChar(const char* in_text, const char* in_text_end); // return number of bytes to express one char in UTF-8 IMGUI_API int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string in UTF-8 IMGUI_API const char* ImTextFindPreviousUtf8Codepoint(const char* in_text_start, const char* in_text_curr); // return previous UTF-8 code-point. +IMGUI_API int ImTextCountLines(const char* in_text, const char* in_text_end); // return number of lines taken by text. trailing carriage return doesn't count as an extra line. // Helpers: File System #ifdef IMGUI_DISABLE_FILE_FUNCTIONS @@ -498,7 +503,8 @@ IMGUI_API ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const IMGUI_API bool ImTriangleContainsPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); IMGUI_API ImVec2 ImTriangleClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p); IMGUI_API void ImTriangleBarycentricCoords(const ImVec2& a, const ImVec2& b, const ImVec2& c, const ImVec2& p, float& out_u, float& out_v, float& out_w); -inline float ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; } +inline float ImTriangleArea(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ImFabs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f; } +inline bool ImTriangleIsClockwise(const ImVec2& a, const ImVec2& b, const ImVec2& c) { return ((b.x - a.x) * (c.y - b.y)) - ((c.x - b.x) * (b.y - a.y)) > 0.0f; } // Helper: ImVec1 (1D vector) // (this odd construct is used to facilitate the transition between 1D and 2D, and the maintenance of some branches/patches) @@ -863,6 +869,7 @@ enum ImGuiInputTextFlagsPrivate_ ImGuiInputTextFlags_Multiline = 1 << 26, // For internal use by InputTextMultiline() ImGuiInputTextFlags_NoMarkEdited = 1 << 27, // For internal use by functions using InputText() before reformatting data ImGuiInputTextFlags_MergedItem = 1 << 28, // For internal use by TempInputText(), will skip calling ItemAdd(). Require bounding-box to strictly match. + ImGuiInputTextFlags_LocalizeDecimalPoint= 1 << 29, // For internal use by InputScalar() and TempInputScalar() }; // Extend ImGuiButtonFlags_ @@ -1111,6 +1118,15 @@ struct IMGUI_API ImGuiInputTextState }; +enum ImGuiWindowRefreshFlags_ +{ + ImGuiWindowRefreshFlags_None = 0, + ImGuiWindowRefreshFlags_TryToAvoidRefresh = 1 << 0, // [EXPERIMENTAL] Try to keep existing contents, USER MUST NOT HONOR BEGIN() RETURNING FALSE AND NOT APPEND. + ImGuiWindowRefreshFlags_RefreshOnHover = 1 << 1, // [EXPERIMENTAL] Always refresh on hover + ImGuiWindowRefreshFlags_RefreshOnFocus = 1 << 2, // [EXPERIMENTAL] Always refresh on focus + // Refresh policy/frequency, Load Balancing etc. +}; + enum ImGuiNextWindowDataFlags_ { ImGuiNextWindowDataFlags_None = 0, @@ -1123,6 +1139,7 @@ enum ImGuiNextWindowDataFlags_ ImGuiNextWindowDataFlags_HasBgAlpha = 1 << 6, ImGuiNextWindowDataFlags_HasScroll = 1 << 7, ImGuiNextWindowDataFlags_HasChildFlags = 1 << 8, + ImGuiNextWindowDataFlags_HasRefreshPolicy = 1 << 9, }; // Storage for SetNexWindow** functions @@ -1144,6 +1161,7 @@ struct ImGuiNextWindowData void* SizeCallbackUserData; float BgAlphaVal; // Override background alpha ImVec2 MenuBarOffsetMinVal; // (Always on) This is not exposed publicly, so we don't clear it and it doesn't have a corresponding flag (could we? for consistency?) + ImGuiWindowRefreshFlags RefreshFlagsVal; ImGuiNextWindowData() { memset(this, 0, sizeof(*this)); } inline void ClearFlags() { Flags = ImGuiNextWindowDataFlags_None; } @@ -1292,7 +1310,7 @@ struct ImGuiPopupData { ImGuiID PopupId; // Set on OpenPopup() ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() - ImGuiWindow* BackupNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close + ImGuiWindow* RestoreNavWindow;// Set on OpenPopup(), a NavWindow that will be restored on popup close int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as "not any of layers" value int OpenFrameCount; // Set on OpenPopup() ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) @@ -1349,7 +1367,6 @@ enum ImGuiInputSource ImGuiInputSource_Mouse, // Note: may be Mouse or TouchScreen or Pen. See io.MouseSource to distinguish them. ImGuiInputSource_Keyboard, ImGuiInputSource_Gamepad, - ImGuiInputSource_Clipboard, // Currently only used by InputText() ImGuiInputSource_COUNT }; @@ -1578,6 +1595,7 @@ enum ImGuiNavMoveFlags_ ImGuiNavMoveFlags_Activate = 1 << 12, // Activate/select target item. ImGuiNavMoveFlags_NoSelect = 1 << 13, // Don't trigger selection by not setting g.NavJustMovedTo ImGuiNavMoveFlags_NoSetNavHighlight = 1 << 14, // Do not alter the visible state of keyboard vs mouse nav highlight + ImGuiNavMoveFlags_NoClearActiveId = 1 << 15, // (Experimental) Do not clear active id when applying move result }; enum ImGuiNavLayer @@ -1594,10 +1612,10 @@ struct ImGuiNavItemData ImGuiID FocusScopeId; // Init,Move // Best candidate focus scope ID ImRect RectRel; // Init,Move // Best candidate bounding box in window relative space ImGuiItemFlags InFlags; // ????,Move // Best candidate item flags - ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionData() value. float DistBox; // Move // Best candidate box distance to current NavId float DistCenter; // Move // Best candidate center distance to current NavId float DistAxial; // Move // Best candidate axial distance to current NavId + ImGuiSelectionUserData SelectionUserData;//I+Mov // Best candidate SetNextItemSelectionData() value. ImGuiNavItemData() { Clear(); } void Clear() { Window = NULL; ID = FocusScopeId = 0; InFlags = 0; SelectionUserData = -1; DistBox = DistCenter = DistAxial = FLT_MAX; } @@ -2530,6 +2548,7 @@ struct IMGUI_API ImGuiWindow bool Collapsed; // Set when collapsing window to become only title-bar bool WantCollapseToggle; bool SkipItems; // Set when items can safely be all clipped (e.g. window not visible or collapsed) + bool SkipRefresh; // [EXPERIMENTAL] Reuse previous frame drawn contents, Begin() returns false. bool Appearing; // Set during the frame where the window is appearing (or re-appearing) bool Hidden; // Do not display (== HiddenFrames*** > 0) bool IsFallbackWindow; // Set on the "Debug##Default" window. @@ -2769,13 +2788,24 @@ struct ImGuiTableColumn }; // Transient cell data stored per row. -// sizeof() ~ 6 +// sizeof() ~ 6 bytes struct ImGuiTableCellData { ImU32 BgColor; // Actual color ImGuiTableColumnIdx Column; // Column number }; +// Parameters for TableAngledHeadersRowEx() +// This may end up being refactored for more general purpose. +// sizeof() ~ 12 bytes +struct ImGuiTableHeaderData +{ + ImGuiTableColumnIdx Index; // Column index + ImU32 TextColor; + ImU32 BgColor0; + ImU32 BgColor1; +}; + // Per-instance data that needs preserving across frames (seemingly most others do not need to be preserved aside from debug needs. Does that means they could be moved to ImGuiTableTempData?) // sizeof() ~ 24 bytes struct ImGuiTableInstanceData @@ -2861,7 +2891,7 @@ struct IMGUI_API ImGuiTable ImGuiTableSortSpecs SortSpecs; // Public facing sorts specs, this is what we return in TableGetSortSpecs() ImGuiTableColumnIdx SortSpecsCount; ImGuiTableColumnIdx ColumnsEnabledCount; // Number of enabled columns (<= ColumnsCount) - ImGuiTableColumnIdx ColumnsEnabledFixedCount; // Number of enabled columns (<= ColumnsCount) + ImGuiTableColumnIdx ColumnsEnabledFixedCount; // Number of enabled columns using fixed width (<= ColumnsCount) ImGuiTableColumnIdx DeclColumnsCount; // Count calls to TableSetupColumn() ImGuiTableColumnIdx AngledHeadersCount; // Count columns with angled headers ImGuiTableColumnIdx HoveredColumnBody; // Index of column whose visible region is being hovered. Important: == ColumnsCount when hovering empty region after the right-most column! @@ -2914,12 +2944,13 @@ struct IMGUI_API ImGuiTable // Transient data that are only needed between BeginTable() and EndTable(), those buffers are shared (1 per level of stacked table). // - Accessing those requires chasing an extra pointer so for very frequently used data we leave them in the main table structure. // - We also leave out of this structure data that tend to be particularly useful for debugging/metrics. -// sizeof() ~ 120 bytes. +// sizeof() ~ 136 bytes. struct IMGUI_API ImGuiTableTempData { int TableIndex; // Index in g.Tables.Buf[] pool float LastTimeActive; // Last timestamp this structure was used float AngledHeadersExtraWidth; // Used in EndTable() + ImVector AngledHeadersRequests; // Used in TableAngledHeadersRow() ImVec2 UserOuterSize; // outer_size.x passed to BeginTable() ImDrawListSplitter DrawSplitter; @@ -2991,6 +3022,7 @@ namespace ImGui IMGUI_API ImGuiWindow* FindWindowByID(ImGuiID id); IMGUI_API ImGuiWindow* FindWindowByName(const char* name); IMGUI_API void UpdateWindowParentAndRootLinks(ImGuiWindow* window, ImGuiWindowFlags flags, ImGuiWindow* parent_window); + IMGUI_API void UpdateWindowSkipRefresh(ImGuiWindow* window); IMGUI_API ImVec2 CalcWindowNextAutoFitSize(ImGuiWindow* window); IMGUI_API bool IsWindowChildOf(ImGuiWindow* window, ImGuiWindow* potential_parent, bool popup_hierarchy); IMGUI_API bool IsWindowWithinBeginStackOf(ImGuiWindow* window, ImGuiWindow* potential_parent); @@ -3016,6 +3048,9 @@ namespace ImGui IMGUI_API int FindWindowDisplayIndex(ImGuiWindow* window); IMGUI_API ImGuiWindow* FindBottomMostVisibleWindowWithinBeginStack(ImGuiWindow* window); + // Windows: Idle, Refresh Policies [EXPERIMENTAL] + IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); + // Fonts, drawing IMGUI_API void SetCurrentFont(ImFont* font); inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } @@ -3303,7 +3338,7 @@ namespace ImGui IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); IMGUI_API void TablePushBackgroundChannel(); IMGUI_API void TablePopBackgroundChannel(); - IMGUI_API void TableAngledHeadersRowEx(float angle, float max_label_width = 0.0f); + IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count); // Tables: Internals inline ImGuiTable* GetCurrentTable() { ImGuiContext& g = *GImGui; return g.CurrentTable; } diff --git a/core/deps/imgui/imgui_tables.cpp b/core/deps/imgui/imgui_tables.cpp index 260df1a92..6815af5ad 100644 --- a/core/deps/imgui/imgui_tables.cpp +++ b/core/deps/imgui/imgui_tables.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.90.6 // (tables and columns code) /* @@ -24,8 +24,9 @@ Index of this file: */ // Navigating this file: -// - In Visual Studio IDE: CTRL+comma ("Edit.GoToAll") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. -// - With Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols in comments. +// - In Visual Studio: CTRL+comma ("Edit.GoToAll") can follow symbols inside comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot. +// - In Visual Studio w/ Visual Assist installed: ALT+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. +// - In VS Code, CLion, etc.: CTRL+click can follow symbols inside comments. //----------------------------------------------------------------------------- // [SECTION] Commentary @@ -227,6 +228,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked @@ -498,6 +500,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG table->DeclColumnsCount = table->AngledHeadersCount = 0; if (previous_frame_active + 1 < g.FrameCount) table->IsActiveIdInTable = false; + table->AngledHeadersHeight = 0.0f; temp_data->AngledHeadersExtraWidth = 0.0f; // Using opaque colors facilitate overlapping lines of the grid, otherwise we'd need to improve TableDrawBorders() @@ -1066,6 +1069,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // - ClipRect.Max.x: using WorkMaxX instead of MaxX (aka including padding) makes things more consistent when resizing down, tho slightly detrimental to visibility in very-small column. // - ClipRect.Max.x: using MaxX makes it easier for header to receive hover highlight with no discontinuity and display sorting arrow. // - FIXME-TABLE: We want equal width columns to have equal (ClipRect.Max.x - WorkMinX) width, which means ClipRect.max.x cannot stray off host_clip_rect.Max.x else right-most column may appear shorter. + const float previous_instance_work_min_x = column->WorkMinX; column->WorkMinX = column->MinX + table->CellPaddingX + table->CellSpacingX1; column->WorkMaxX = column->MaxX - table->CellPaddingX - table->CellSpacingX2; // Expected max column->ItemWidth = ImTrunc(column->WidthGiven * 0.65f); @@ -1118,8 +1122,22 @@ void ImGui::TableUpdateLayout(ImGuiTable* table) // column->WorkMinX = ImLerp(column->WorkMinX, ImMax(column->StartX, column->MaxX - column->ContentWidthRowsUnfrozen), 0.5f); // Reset content width variables - column->ContentMaxXFrozen = column->ContentMaxXUnfrozen = column->WorkMinX; - column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX; + if (table->InstanceCurrent == 0) + { + column->ContentMaxXFrozen = column->WorkMinX; + column->ContentMaxXUnfrozen = column->WorkMinX; + column->ContentMaxXHeadersUsed = column->WorkMinX; + column->ContentMaxXHeadersIdeal = column->WorkMinX; + } + else + { + // As we store an absolute value to make per-cell updates faster, we need to offset values used for width computation. + const float offset_from_previous_instance = column->WorkMinX - previous_instance_work_min_x; + column->ContentMaxXFrozen += offset_from_previous_instance; + column->ContentMaxXUnfrozen += offset_from_previous_instance; + column->ContentMaxXHeadersUsed += offset_from_previous_instance; + column->ContentMaxXHeadersIdeal += offset_from_previous_instance; + } // Don't decrement auto-fit counters until container window got a chance to submit its items if (table->HostSkipItems == false) @@ -1240,7 +1258,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table) ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); const float hit_half_width = TABLE_RESIZE_SEPARATOR_HALF_THICKNESS; const float hit_y1 = (table->FreezeRowsCount >= 1 ? table->OuterRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight; - const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table_instance->LastOuterHeight); + const float hit_y2_body = ImMax(table->OuterRect.Max.y, hit_y1 + table_instance->LastOuterHeight - table->AngledHeadersHeight); const float hit_y2_head = hit_y1 + table_instance->LastTopHeadersRowHeight; for (int order_n = 0; order_n < table->ColumnsCount; order_n++) @@ -1890,7 +1908,7 @@ void ImGui::TableEndRow(ImGuiTable* table) if (is_visible) { // Update data for TableGetHoveredRow() - if (table->HoveredColumnBody != -1 && g.IO.MousePos.y >= bg_y1 && g.IO.MousePos.y < bg_y2) + if (table->HoveredColumnBody != -1 && g.IO.MousePos.y >= bg_y1 && g.IO.MousePos.y < bg_y2 && table_instance->HoveredRowNext < 0) table_instance->HoveredRowNext = table->CurrentRow; // Decide of background color for the row @@ -3153,15 +3171,43 @@ void ImGui::TableHeader(const char* label) } // Unlike TableHeadersRow() it is not expected that you can reimplement or customize this with custom widgets. -// FIXME: highlight without ImGuiTableFlags_HighlightHoveredColumn // FIXME: No hit-testing/button on the angled header. void ImGui::TableAngledHeadersRow() { ImGuiContext& g = *GImGui; - TableAngledHeadersRowEx(g.Style.TableAngledHeadersAngle, 0.0f); + ImGuiTable* table = g.CurrentTable; + ImGuiTableTempData* temp_data = table->TempData; + temp_data->AngledHeadersRequests.resize(0); + temp_data->AngledHeadersRequests.reserve(table->ColumnsEnabledCount); + + // Which column needs highlight? + const ImGuiID row_id = GetID("##AngledHeaders"); + ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); + int highlight_column_n = table->HighlightColumnHeader; + if (highlight_column_n == -1 && table->HoveredColumnBody != -1) + if (table_instance->HoveredRowLast == 0 && table->HoveredColumnBorder == -1 && (g.ActiveId == 0 || g.ActiveId == row_id || (table->IsActiveIdInTable || g.DragDropActive))) + highlight_column_n = table->HoveredColumnBody; + + // Build up request + ImU32 col_header_bg = GetColorU32(ImGuiCol_TableHeaderBg); + ImU32 col_text = GetColorU32(ImGuiCol_Text); + for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + if (IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) + { + const int column_n = table->DisplayOrderToIndex[order_n]; + ImGuiTableColumn* column = &table->Columns[column_n]; + if ((column->Flags & ImGuiTableColumnFlags_AngledHeader) == 0) // Note: can't rely on ImGuiTableColumnFlags_IsVisible test here. + continue; + ImGuiTableHeaderData request = { (ImGuiTableColumnIdx)column_n, col_text, col_header_bg, (column_n == highlight_column_n) ? GetColorU32(ImGuiCol_Header) : 0 }; + temp_data->AngledHeadersRequests.push_back(request); + } + + // Render row + TableAngledHeadersRowEx(row_id, g.Style.TableAngledHeadersAngle, 0.0f, temp_data->AngledHeadersRequests.Data, temp_data->AngledHeadersRequests.Size); } -void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) +// Important: data must be fed left to right +void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count) { ImGuiContext& g = *GImGui; ImGuiTable* table = g.CurrentTable; @@ -3185,7 +3231,7 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) // Calculate our base metrics and set angled headers data _before_ the first call to TableNextRow() // FIXME-STYLE: Would it be better for user to submit 'max_label_width' or 'row_height' ? One can be derived from the other. const float header_height = g.FontSize + g.Style.CellPadding.x * 2.0f; - const float row_height = ImFabs(ImRotate(ImVec2(max_label_width, flip_label ? +header_height : -header_height), cos_a, sin_a).y); + const float row_height = ImTrunc(ImFabs(ImRotate(ImVec2(max_label_width, flip_label ? +header_height : -header_height), cos_a, sin_a).y)); table->AngledHeadersHeight = row_height; table->AngledHeadersSlope = (sin_a != 0.0f) ? (cos_a / sin_a) : 0.0f; const ImVec2 header_angled_vector = unit_right * (row_height / -sin_a); // vector from bottom-left to top-left, and from bottom-right to top-right @@ -3203,28 +3249,22 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) draw_list->AddRectFilled(ImVec2(table->BgClipRect.Min.x, row_r.Min.y), ImVec2(table->BgClipRect.Max.x, row_r.Max.y), GetColorU32(ImGuiCol_TableHeaderBg, 0.25f)); // FIXME-STYLE: Change row background with an arbitrary color. PushClipRect(ImVec2(clip_rect_min_x, table->BgClipRect.Min.y), table->BgClipRect.Max, true); // Span all columns - const ImGuiID row_id = GetID("##AngledHeaders"); ButtonBehavior(row_r, row_id, NULL, NULL); KeepAliveID(row_id); - ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); - int highlight_column_n = table->HighlightColumnHeader; - if (highlight_column_n == -1 && table->HoveredColumnBody != -1) - if (table_instance->HoveredRowLast == 0 && table->HoveredColumnBorder == -1 && (g.ActiveId == 0 || g.ActiveId == row_id || (table->IsActiveIdInTable || g.DragDropActive))) - highlight_column_n = table->HoveredColumnBody; + const float ascent_scaled = g.Font->Ascent * (g.FontSize / g.Font->FontSize); // FIXME: Standardize those scaling factors better + const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); + const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component + const ImVec2 align = g.Style.TableAngledHeadersTextAlign; // Draw background and labels in first pass, then all borders. float max_x = 0.0f; - ImVec2 padding = g.Style.CellPadding; // We will always use swapped component for (int pass = 0; pass < 2; pass++) - for (int order_n = 0; order_n < table->ColumnsCount; order_n++) + for (int order_n = 0; order_n < data_count; order_n++) { - if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n)) - continue; - const int column_n = table->DisplayOrderToIndex[order_n]; + const ImGuiTableHeaderData* request = &data[order_n]; + const int column_n = request->Index; ImGuiTableColumn* column = &table->Columns[column_n]; - if ((column->Flags & ImGuiTableColumnFlags_AngledHeader) == 0) // Note: can't rely on ImGuiTableColumnFlags_IsVisible test here. - continue; ImVec2 bg_shape[4]; bg_shape[0] = ImVec2(column->MaxX, row_r.Max.y); @@ -3234,9 +3274,8 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) if (pass == 0) { // Draw shape - draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], GetColorU32(ImGuiCol_TableHeaderBg)); - if (column_n == highlight_column_n) - draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], GetColorU32(ImGuiCol_Header)); // Highlight on hover + draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request->BgColor0); + draw_list->AddQuadFilled(bg_shape[0], bg_shape[1], bg_shape[2], bg_shape[3], request->BgColor1); // Optional highlight max_x = ImMax(max_x, bg_shape[3].x); // Draw label @@ -3244,8 +3283,17 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) // - Handle multiple lines manually, as we want each lines to follow on the horizontal border, rather than see a whole block rotated. const char* label_name = TableGetColumnName(table, column_n); const char* label_name_end = FindRenderedTextEnd(label_name); - const float line_off_step_x = g.FontSize / -sin_a; - float line_off_curr_x = 0.0f; + const float line_off_step_x = (g.FontSize / -sin_a); + const int label_lines = ImTextCountLines(label_name, label_name_end); + + // Left<>Right alignment + float line_off_curr_x = flip_label ? (label_lines - 1) * line_off_step_x : 0.0f; + float line_off_for_align_x = ImMax((((column->MaxX - column->MinX) - padding.x * 2.0f) - (label_lines * line_off_step_x)), 0.0f) * align.x; + line_off_curr_x += line_off_for_align_x - line_off_for_ascent_x; + + // Register header width + column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX + ImCeil(label_lines * line_off_step_x - line_off_for_align_x); + while (label_name < label_name_end) { const char* label_name_eol = strchr(label_name, '\n'); @@ -3258,22 +3306,26 @@ void ImGui::TableAngledHeadersRowEx(float angle, float max_label_width) float clip_height = ImMin(label_size.y, column->ClipRect.Max.x - column->WorkMinX - line_off_curr_x); ImRect clip_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width, clip_height)); int vtx_idx_begin = draw_list->_VtxCurrentIdx; + PushStyleColor(ImGuiCol_Text, request->TextColor); RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, clip_r.Max.x, label_name, label_name_eol, &label_size); + PopStyleColor(); int vtx_idx_end = draw_list->_VtxCurrentIdx; + // Up<>Down alignment + const float available_space = ImMax(clip_width - label_size.x + ImAbs(padding.x * cos_a) * 2.0f - ImAbs(padding.y * sin_a) * 2.0f, 0.0f); + const float vertical_offset = available_space * align.y * (flip_label ? -1.0f : 1.0f); + // Rotate and offset label - ImVec2 pivot_in = ImVec2(window->ClipRect.Min.x, window->ClipRect.Min.y + label_size.y); + ImVec2 pivot_in = ImVec2(window->ClipRect.Min.x - vertical_offset, window->ClipRect.Min.y + label_size.y); ImVec2 pivot_out = ImVec2(column->WorkMinX, row_r.Max.y); - line_off_curr_x += line_off_step_x; + line_off_curr_x += flip_label ? -line_off_step_x : line_off_step_x; pivot_out += unit_right * padding.y; if (flip_label) pivot_out += unit_right * (clip_width - ImMax(0.0f, clip_width - label_size.x)); - pivot_out.x += flip_label ? line_off_curr_x - line_off_step_x : line_off_curr_x; + pivot_out.x += flip_label ? line_off_curr_x + line_off_step_x : line_off_curr_x; ShadeVertsTransformPos(draw_list, vtx_idx_begin, vtx_idx_end, pivot_in, label_cos_a, label_sin_a, pivot_out); // Rotate and offset - //if (g.IO.KeyShift) { ImDrawList* fg_dl = GetForegroundDrawList(); vtx_idx_begin = fg_dl->_VtxCurrentIdx; fg_dl->AddRect(clip_r.Min, clip_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 2.0f); ShadeVertsTransformPos(fg_dl, vtx_idx_begin, fg_dl->_VtxCurrentIdx, pivot_in, label_cos_a, label_sin_a, pivot_out); } + //if (g.IO.KeyShift) { ImDrawList* fg_dl = GetForegroundDrawList(); vtx_idx_begin = fg_dl->_VtxCurrentIdx; fg_dl->AddRect(clip_r.Min, clip_r.Max, IM_COL32(0, 255, 0, 255), 0.0f, 0, 1.0f); ShadeVertsTransformPos(fg_dl, vtx_idx_begin, fg_dl->_VtxCurrentIdx, pivot_in, label_cos_a, label_sin_a, pivot_out); } - // Register header width - column->ContentMaxXHeadersUsed = column->ContentMaxXHeadersIdeal = column->WorkMinX + ImCeil(line_off_curr_x); label_name = label_name_eol + 1; } } diff --git a/core/deps/imgui/imgui_widgets.cpp b/core/deps/imgui/imgui_widgets.cpp index 5ce24b417..f4c1a900d 100644 --- a/core/deps/imgui/imgui_widgets.cpp +++ b/core/deps/imgui/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.90.4 +// dear imgui, v1.90.6 // (widgets code) /* @@ -75,6 +75,7 @@ Index of this file: #pragma clang diagnostic ignored "-Wenum-enum-conversion" // warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') #pragma clang diagnostic ignored "-Wdeprecated-enum-enum-conversion"// warning: bitwise operation between different enumeration types ('XXXFlags_' and 'XXXFlagsPrivate_') is deprecated #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" // warning: 'xxx' is an unsafe pointer used for buffer access #elif defined(__GNUC__) #pragma GCC diagnostic ignored "-Wpragmas" // warning: unknown option after '#pragma GCC diagnostic' kind #pragma GCC diagnostic ignored "-Wformat-nonliteral" // warning: format not a string literal, format string not checked @@ -122,9 +123,9 @@ static const ImU64 IM_U64_MAX = (2ULL * 9223372036854775807LL + 1); //------------------------------------------------------------------------- // For InputTextEx() -static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source); -static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); -static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); +static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false); +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end); +static ImVec2 InputTextCalcTextSizeW(ImGuiContext* ctx, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false); //------------------------------------------------------------------------- // [SECTION] Widgets: Text, etc. @@ -508,7 +509,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool #ifdef IMGUI_ENABLE_TEST_ENGINE // Alternate registration spot, for when caller didn't use ItemAdd() - if (id != 0 && g.LastItemData.ID != id) + if (g.LastItemData.ID != id) IMGUI_TEST_ENGINE_ITEM_ADD(id, bb, NULL); #endif @@ -536,6 +537,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool const ImGuiID test_owner_id = (flags & ImGuiButtonFlags_NoTestKeyOwner) ? ImGuiKeyOwner_Any : id; if (hovered) { + IM_ASSERT(id != 0); // Lazily check inside rare path. + // Poll mouse buttons // - 'mouse_button_clicked' is generally carried into ActiveIdMouseButton when setting ActiveId. // - Technically we only need some values in one code path, but since this is gated by hovered test this is fine. @@ -1312,24 +1315,47 @@ void ImGui::ProgressBar(float fraction, const ImVec2& size_arg, const char* over if (!ItemAdd(bb, 0)) return; - // Render - fraction = ImSaturate(fraction); - RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); - bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); - const ImVec2 fill_br = ImVec2(ImLerp(bb.Min.x, bb.Max.x, fraction), bb.Max.y); - RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), 0.0f, fraction, style.FrameRounding); + // Fraction < 0.0f will display an indeterminate progress bar animation + // The value must be animated along with time, so e.g. passing '-1.0f * ImGui::GetTime()' as fraction works. + const bool is_indeterminate = (fraction < 0.0f); + if (!is_indeterminate) + fraction = ImSaturate(fraction); - // Default displaying the fraction as percentage string, but user can override it - char overlay_buf[32]; - if (!overlay) + // Out of courtesy we accept a NaN fraction without crashing + float fill_n0 = 0.0f; + float fill_n1 = (fraction == fraction) ? fraction : 0.0f; + + if (is_indeterminate) { - ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); - overlay = overlay_buf; + const float fill_width_n = 0.2f; + fill_n0 = ImFmod(-fraction, 1.0f) * (1.0f + fill_width_n) - fill_width_n; + fill_n1 = ImSaturate(fill_n0 + fill_width_n); + fill_n0 = ImSaturate(fill_n0); } - ImVec2 overlay_size = CalcTextSize(overlay, NULL); - if (overlay_size.x > 0.0f) - RenderTextClipped(ImVec2(ImClamp(fill_br.x + style.ItemSpacing.x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); + // Render + RenderFrame(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); + bb.Expand(ImVec2(-style.FrameBorderSize, -style.FrameBorderSize)); + RenderRectFilledRangeH(window->DrawList, bb, GetColorU32(ImGuiCol_PlotHistogram), fill_n0, fill_n1, style.FrameRounding); + + // Default displaying the fraction as percentage string, but user can override it + // Don't display text for indeterminate bars by default + char overlay_buf[32]; + if (!is_indeterminate || overlay != NULL) + { + if (!overlay) + { + ImFormatString(overlay_buf, IM_ARRAYSIZE(overlay_buf), "%.0f%%", fraction * 100 + 0.01f); + overlay = overlay_buf; + } + + ImVec2 overlay_size = CalcTextSize(overlay, NULL); + if (overlay_size.x > 0.0f) + { + float text_x = is_indeterminate ? (bb.Min.x + bb.Max.x - overlay_size.x) * 0.5f : ImLerp(bb.Min.x, bb.Max.x, fill_n1) + style.ItemSpacing.x; + RenderTextClipped(ImVec2(ImClamp(text_x, bb.Min.x, bb.Max.x - overlay_size.x - style.ItemInnerSpacing.x), bb.Min.y), bb.Max, overlay, NULL, &overlay_size, ImVec2(0.0f, 0.5f), &bb); + } + } } void ImGui::Bullet() @@ -3450,7 +3476,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, p_data, format); ImStrTrimBlanks(data_buf); - ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; bool value_changed = false; if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags)) @@ -3495,6 +3521,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format); flags |= ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. + flags |= (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint; bool value_changed = false; if (p_step == NULL) @@ -3940,9 +3967,8 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons } // Return false to discard a character. -static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, ImGuiInputSource input_source) +static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard) { - IM_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard); unsigned int c = *p_char; // Filter non-printable (NB: isprint is unreliable! see #2467) @@ -3957,7 +3983,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted. } - if (input_source != ImGuiInputSource_Clipboard) + if (input_source_is_clipboard == false) { // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817) if (c == 127) @@ -3973,7 +3999,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im return false; // Generic named filters - if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific))) + if (apply_named_filters && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsUppercase | ImGuiInputTextFlags_CharsNoBlank | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint))) { // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'. // The standard mandate that programs starts in the "C" locale where the decimal point is '.'. @@ -3983,7 +4009,7 @@ static bool InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, Im // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (is_word_boundary_from_right/is_word_boundary_from_left) functions. ImGuiContext& g = *ctx; const unsigned c_decimal_point = (unsigned int)g.IO.PlatformLocaleDecimalPoint; - if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific)) + if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint)) if (c == '.' || c == ',') c = c_decimal_point; @@ -4442,7 +4468,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (Shortcut(ImGuiKey_Tab, id, ImGuiInputFlags_Repeat)) { unsigned int c = '\t'; // Insert TAB - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } // FIXME: Implement Shift+Tab @@ -4465,7 +4491,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ unsigned int c = (unsigned int)io.InputQueueCharacters[n]; if (c == '\t') // Skip Tab, see above. continue; - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } @@ -4548,7 +4574,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else if (!is_readonly) { unsigned int c = '\n'; // Insert new line - if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard)) + if (InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } } @@ -4615,7 +4641,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ { unsigned int c; s += ImTextCharFromUtf8(&c, s, NULL); - if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, ImGuiInputSource_Clipboard)) + if (!InputTextFilterCharacter(&g, &c, flags, callback, callback_user_data, true)) continue; clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; } @@ -6203,13 +6229,17 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l label_end = FindRenderedTextEnd(label); const ImVec2 label_size = CalcTextSize(label, label_end, false); + const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing + const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it + const float text_width = g.FontSize + label_size.x + padding.x * 2; // Include collapsing arrow + // We vertically grow up to current line height up the typical widget height. const float frame_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), label_size.y + padding.y * 2); const bool span_all_columns = (flags & ImGuiTreeNodeFlags_SpanAllColumns) != 0 && (g.CurrentTable != NULL); ImRect frame_bb; frame_bb.Min.x = span_all_columns ? window->ParentWorkRect.Min.x : (flags & ImGuiTreeNodeFlags_SpanFullWidth) ? window->WorkRect.Min.x : window->DC.CursorPos.x; frame_bb.Min.y = window->DC.CursorPos.y; - frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : window->WorkRect.Max.x; + frame_bb.Max.x = span_all_columns ? window->ParentWorkRect.Max.x : (flags & ImGuiTreeNodeFlags_SpanTextWidth) ? window->DC.CursorPos.x + text_width + padding.x : window->WorkRect.Max.x; frame_bb.Max.y = window->DC.CursorPos.y + frame_height; if (display_frame) { @@ -6219,16 +6249,13 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l frame_bb.Max.x += IM_TRUNC(window->WindowPadding.x * 0.5f); } - const float text_offset_x = g.FontSize + (display_frame ? padding.x * 3 : padding.x * 2); // Collapsing arrow width + Spacing - const float text_offset_y = ImMax(padding.y, window->DC.CurrLineTextBaseOffset); // Latch before ItemSize changes it - const float text_width = g.FontSize + (label_size.x > 0.0f ? label_size.x + padding.x * 2 : 0.0f); // Include collapsing ImVec2 text_pos(window->DC.CursorPos.x + text_offset_x, window->DC.CursorPos.y + text_offset_y); ItemSize(ImVec2(text_width, frame_height), padding.y); // For regular tree nodes, we arbitrary allow to click past 2 worth of ItemSpacing ImRect interact_bb = frame_bb; - if (!display_frame && (flags & (ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) - interact_bb.Max.x = frame_bb.Min.x + text_width + style.ItemSpacing.x * 2.0f; + if ((flags & (ImGuiTreeNodeFlags_Framed | ImGuiTreeNodeFlags_SpanAvailWidth | ImGuiTreeNodeFlags_SpanFullWidth | ImGuiTreeNodeFlags_SpanTextWidth | ImGuiTreeNodeFlags_SpanAllColumns)) == 0) + interact_bb.Max.x = frame_bb.Min.x + text_width + (label_size.x > 0.0f ? style.ItemSpacing.x * 2.0f : 0.0f); // Modify ClipRect for the ItemAdd(), faster than doing a PushColumnsBackground/PushTableBackgroundChannel for every Selectable.. const float backup_clip_rect_min_x = window->ClipRect.Min.x; @@ -6958,6 +6985,7 @@ bool ImGui::BeginListBox(const char* label, const ImVec2& size_arg) ImVec2 label_pos = ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y); RenderText(label_pos, label); window->DC.CursorMaxPos = ImMax(window->DC.CursorMaxPos, label_pos + label_size); + AlignTextToFramePadding(); } BeginChild(id, frame_bb.GetSize(), ImGuiChildFlags_FrameStyle); From f05e5f35824e347507b0733a283db50ad9008568 Mon Sep 17 00:00:00 2001 From: scribam Date: Sun, 19 May 2024 20:13:43 +0200 Subject: [PATCH 84/86] deps: update libchdr --- core/deps/libchdr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/deps/libchdr b/core/deps/libchdr index 7239eab39..86b272076 160000 --- a/core/deps/libchdr +++ b/core/deps/libchdr @@ -1 +1 @@ -Subproject commit 7239eab39c961a27959cfb58c8877cb2ab1fbf2d +Subproject commit 86b272076d542287d3f03952e7d4efe283e815bf From 20652516e78e926822b48bc0dd7fb000ed859f6c Mon Sep 17 00:00:00 2001 From: scribam Date: Sun, 19 May 2024 20:13:43 +0200 Subject: [PATCH 85/86] deps: update sdl to version 2.30.3 --- core/deps/SDL | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/deps/SDL b/core/deps/SDL index f461d91cd..fb1497566 160000 --- a/core/deps/SDL +++ b/core/deps/SDL @@ -1 +1 @@ -Subproject commit f461d91cd265d7b9a44b4d472b1df0c0ad2855a0 +Subproject commit fb1497566c5a05e2babdcf45ef0ab5c7cca2c4ae From a5608f4f22d63ee47d8ef690ed2e7eeb97a9574b Mon Sep 17 00:00:00 2001 From: scribam Date: Sun, 19 May 2024 21:27:53 +0200 Subject: [PATCH 86/86] android: update project - Upgrade AGP dependency from 8.3.0 to 8.4.1 - Upgrade Gradle version to 8.6 - Use version catalogs --- shell/android-studio/build.gradle | 7 +------ shell/android-studio/flycast/build.gradle | 12 ++++++------ .../flycast/src/main/AndroidManifest.xml | 4 +++- shell/android-studio/gradle.properties | 4 ++-- shell/android-studio/gradle/libs.versions.toml | 17 +++++++++++++++++ .../gradle/wrapper/gradle-wrapper.properties | 4 ++-- shell/android-studio/settings.gradle | 8 +++++++- 7 files changed, 38 insertions(+), 18 deletions(-) create mode 100644 shell/android-studio/gradle/libs.versions.toml diff --git a/shell/android-studio/build.gradle b/shell/android-studio/build.gradle index 55902fd12..565f8c24e 100644 --- a/shell/android-studio/build.gradle +++ b/shell/android-studio/build.gradle @@ -1,9 +1,4 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.3.0' apply false - id 'com.android.library' version '8.3.0' apply false -} - -task clean(type: Delete) { - delete rootProject.buildDir +alias(libs.plugins.android.application) apply false } \ No newline at end of file diff --git a/shell/android-studio/flycast/build.gradle b/shell/android-studio/flycast/build.gradle index 5d0e455ba..09148ef49 100644 --- a/shell/android-studio/flycast/build.gradle +++ b/shell/android-studio/flycast/build.gradle @@ -1,5 +1,5 @@ plugins { - id 'com.android.application' + alias(libs.plugins.android.application) } def getVersionName = { -> @@ -80,10 +80,10 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'org.apache.commons:commons-lang3:3.12.0' - implementation 'org.apache.httpcomponents.client5:httpclient5:5.0.3' - implementation 'org.slf4j:slf4j-android:1.7.35' + implementation libs.appcompat + implementation libs.commons.lang3 + implementation libs.httpclient5 + implementation libs.slf4j.android implementation fileTree(dir: 'libs', include: ['*.aar', '*.jar'], exclude: []) - implementation 'androidx.documentfile:documentfile:1.0.1' + implementation libs.documentfile } diff --git a/shell/android-studio/flycast/src/main/AndroidManifest.xml b/shell/android-studio/flycast/src/main/AndroidManifest.xml index 63c30b1fd..5b9f6e183 100644 --- a/shell/android-studio/flycast/src/main/AndroidManifest.xml +++ b/shell/android-studio/flycast/src/main/AndroidManifest.xml @@ -1,4 +1,6 @@ - + +