Update to spice2x-23-06-08

This commit is contained in:
[ ]
2023-09-20 02:09:17 +09:00
parent 8957d9d446
commit d03cca1fe2
98 changed files with 21957 additions and 4977 deletions
+4
View File
@@ -1,3 +1,5 @@
.vscode/*
# JetBrains IDEs
.idea
.idea_modules
@@ -13,3 +15,5 @@ build_docker.local.sh
# output from build script
bin/*
dist/*
external/cv2pdb/*
+10
View File
@@ -10,6 +10,9 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
# niceities for vscode
set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE)
# for RapidJSON
add_compile_definitions(RAPIDJSON_HAS_STDSTRING)
if(MSVC)
# disable intermediate manifest
@@ -419,6 +422,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
hooks/debughook.cpp
hooks/devicehook.cpp
hooks/graphics/graphics.cpp
hooks/graphics/graphics_windowed.cpp
hooks/graphics/backends/d3d9/d3d9_backend.cpp
hooks/graphics/backends/d3d9/d3d9_device.cpp
hooks/graphics/backends/d3d9/d3d9_fake_swapchain.cpp
@@ -471,7 +475,13 @@ set(SOURCE_FILES ${SOURCE_FILES}
overlay/windows/control.cpp
overlay/windows/eadev.cpp
overlay/windows/fps.cpp
overlay/windows/generic_sub.cpp
overlay/windows/iidx_seg.cpp
overlay/windows/iidx_sub.cpp
overlay/windows/iopanel.cpp
overlay/windows/iopanel_ddr.cpp
overlay/windows/iopanel_gfdm.cpp
overlay/windows/iopanel_iidx.cpp
overlay/windows/keypad.cpp
overlay/windows/kfcontrol.cpp
overlay/windows/log.cpp
+5 -4
View File
@@ -2,6 +2,7 @@
#include "avs/game.h"
#include "games/ddr/io.h"
#include "games/sdvx/sdvx.h"
#include "games/sdvx/io.h"
#include "games/drs/io.h"
#include "games/drs/drs.h"
@@ -92,18 +93,18 @@ static bool __cdecl ac_io_bi2a_update_control_status_buffer() {
// volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
BI2A_VOLL = (BI2A_VOLL - 16) & 1023;
BI2A_VOLL = (BI2A_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
BI2A_VOLL = (BI2A_VOLL + 16) & 1023;
BI2A_VOLL = (BI2A_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
BI2A_VOLR = (BI2A_VOLR - 16) & 1023;
BI2A_VOLR = (BI2A_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
BI2A_VOLR = (BI2A_VOLR + 16) & 1023;
BI2A_VOLR = (BI2A_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// update volumes
+5 -4
View File
@@ -4,6 +4,7 @@
#include "games/bs/io.h"
#include "games/nost/io.h"
#include "games/scotto/io.h"
#include "games/sdvx/sdvx.h"
#include "games/sdvx/io.h"
#include "misc/eamuse.h"
#include "rawinput/rawinput.h"
@@ -342,18 +343,18 @@ static char __cdecl ac_io_kfca_update_control_status_buffer() {
// volume left
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Left))) {
KFCA_VOLL = (KFCA_VOLL - 16) & 1023;
KFCA_VOLL = (KFCA_VOLL - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_L_Right))) {
KFCA_VOLL = (KFCA_VOLL + 16) & 1023;
KFCA_VOLL = (KFCA_VOLL + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// volume right
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Left))) {
KFCA_VOLR = (KFCA_VOLR - 16) & 1023;
KFCA_VOLR = (KFCA_VOLR - games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
if (Buttons::getState(RI_MGR, buttons.at(games::sdvx::Buttons::VOL_R_Right))) {
KFCA_VOLR = (KFCA_VOLR + 16) & 1023;
KFCA_VOLR = (KFCA_VOLR + games::sdvx::DIGITAL_KNOB_SENS) & 1023;
}
// update volumes
+22 -14
View File
@@ -8,6 +8,7 @@
#include "launcher/launcher.h"
#include "touch/touch.h"
#include "util/utils.h"
#include "games/iidx/iidx.h"
using namespace std::placeholders;
using namespace rapidjson;
@@ -16,6 +17,9 @@ using namespace rapidjson;
namespace api::modules {
Touch::Touch() : Module("touch") {
is_sdvx = avs::game::is_model("KFC");
is_tdj_fhd = (avs::game::is_model("LDJ") && games::iidx::is_tdj_fhd());
functions["read"] = std::bind(&Touch::read, this, _1, _2);
functions["write"] = std::bind(&Touch::write, this, _1, _2);
functions["write_reset"] = std::bind(&Touch::write_reset, this, _1, _2);
@@ -78,20 +82,8 @@ namespace api::modules {
auto touch_x = param[1].GetInt();
auto touch_y = param[2].GetInt();
// TODO: move this check somewhere better
if (avs::game::is_model("KFC")) {
apply_touch_errata(touch_x, touch_y);
// mirror
touch_points.emplace_back(TouchPoint {
.id = touch_id,
.x = 1080 - touch_y,
.y = touch_x,
.mouse = false,
});
} else {
// normal
touch_points.emplace_back(TouchPoint {
.id = touch_id,
.x = touch_x,
@@ -99,7 +91,6 @@ namespace api::modules {
.mouse = false,
});
}
}
// apply touch points
touch_write_points(&touch_points);
@@ -128,4 +119,21 @@ namespace api::modules {
// remove all IDs
touch_remove_points(&touch_point_ids);
}
void Touch::apply_touch_errata(int &x, int &y) {
int x_raw = x;
int y_raw = y;
if (is_tdj_fhd) {
// deal with TDJ FHD resolution mismatch (upgrade 720p to 1080p)
// we don't know what screen is being shown on the companion and the API doesn't specify
// the target of the touch events so just assume it's the sub screen
x = x_raw * 1920 / 1280;
y = y_raw * 1080 / 720;
} else if (is_sdvx) {
// for exceed gear, they are both 1080p screens, but need to apply transformation
x = 1080 - y_raw;
y = x_raw;
}
}
}
+4
View File
@@ -11,10 +11,14 @@ namespace api::modules {
Touch();
private:
bool is_sdvx;
bool is_tdj_fhd;
// function definitions
void read(Request &req, Response &res);
void write(Request &req, Response &res);
void write_reset(Request &req, Response &res);
void apply_touch_errata(int &x, int &y);
};
}
+1
View File
@@ -6,3 +6,4 @@
#define IDR_LICENSES 131
#define IDR_PATCHES 132
#define IDR_README 133
#define IDR_DSEGFONT 134
+9 -6
View File
@@ -29,14 +29,17 @@ BUILDDIR_32_DEBUG="./cmake-build-debug-32"
BUILDDIR_64_RELEASE="./cmake-build-release-64"
BUILDDIR_64_DEBUG="./cmake-build-debug-64"
DEBUG=0
OUTDIR="./bin/spicetools"
UPX_ENABLE=1
OUTDIR="./bin/spice2x"
# disabled UPX since it tends to falsely trigger malware detection
UPX_ENABLE=0
UPX_FLAGS="-q --best --lzma --compress-exports=0"
CLEAN_BUILD=1
INCLUDE_SRC=1
DIST_ENABLE=1
DIST_FOLDER="./dist"
DIST_NAME="spicetools-$(date +%y)-$(date +%m)-$(date +%d).zip"
DIST_NAME="spice2x-$(date +%y)-$(date +%m)-$(date +%d).zip"
DIST_COMMENT=${DIST_NAME}$'\n'"$GIT_BRANCH - $GIT_HEAD"$'\nThank you for playing.'
TARGETS_32="spicetools_stubs_kbt spicetools_stubs_kld spicetools_cfg spicetools_spice"
TARGETS_64="spicetools_stubs_kbt64 spicetools_stubs_kld64 spicetools_spice64"
@@ -101,7 +104,7 @@ echo "Compilation process done :)"
echo "==========================="
# generate PDBs
if ((DEBUG > 0))
if false # ((DEBUG > 0))
then
echo "Generating PDBs..."
@@ -150,7 +153,7 @@ rm -rf ${OUTDIR}
mkdir -p ${OUTDIR}
#mkdir -p ${OUTDIR}/stubs/32
#mkdir -p ${OUTDIR}/stubs/64
if ((DEBUG > 0))
if false # ((DEBUG > 0))
then
# debug files
cp ${BUILDDIR_32}/spicetools/spicecfg-pdb.exe ${OUTDIR} 2>/dev/null
@@ -193,7 +196,7 @@ mkdir -p ${OUTDIR}/src
if ((INCLUDE_SRC > 0))
then
echo "Generating source file archive..."
git archive --format tar --prefix=spicetools/ HEAD | gzip -9 > ${OUTDIR}/src/spicetools-${GIT_BRANCH}.tar.gz 2>/dev/null || \
git archive --format tar.gz --prefix=spice2x/ HEAD > ${OUTDIR}/src/spice2x-${GIT_BRANCH}.tar.gz 2>/dev/null || \
echo "WARNING: Couldn't get git to create the archive. Is this a git repository?"
fi
+7
View File
@@ -0,0 +1,7 @@
docker build --pull external/docker -t spicetools/deps
docker build . -t spicetools/spice --no-cache
docker run --rm -it -v %cd%/dist:/src/dist -v %cd%/bin:/src/bin spicetools/spice
@REM to generate PDBs, set DEBUG to 1 in build_all.sh, place cv2pdb in external\cv2pdb, and run below
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spicecfg.exe bin\spice2x\spicecfg-pdb.exe bin\spice2x\spicecfg-pdb.pdb
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice.exe bin\spice2x\spice-pdb.exe bin\spice2x\spice-pdb.pdb
@REM external\cv2pdb\cv2pdb.exe bin\spice2x\spice64.exe bin\spice2x\spice64-pdb.exe bin\spice2x\spice64-pdb.pdb
+1
View File
@@ -13,5 +13,6 @@ IDR_CHANGELOG RCDATA "../changelog.txt"
IDR_LICENSES RCDATA "../licenses.txt"
IDR_PATCHES RCDATA "../build/patches.json"
IDR_README RCDATA "../readme.txt"
IDR_DSEGFONT RCDATA "../external/dseg/DSEG14Classic-Italic.ttf"
#endif
+5 -1
View File
@@ -29,7 +29,7 @@ cfg::ConfiguratorWindow::ConfiguratorWindow() {
// determine window title
if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::Config) {
WINDOW_TITLE = "SpiceTools Config (" + to_string(VERSION_STRING_CFG) + ")";
WINDOW_TITLE = "spice2x config - a fork of SpiceTools (" + to_string(VERSION_STRING_CFG) + ")";
WINDOW_SIZE_X = 800;
WINDOW_SIZE_Y = 600;
} else if (cfg::CONFIGURATOR_TYPE == cfg::ConfigType::KFControl) {
@@ -49,6 +49,10 @@ cfg::ConfiguratorWindow::ConfiguratorWindow() {
NULL,
GetModuleHandle(NULL),
(LPVOID) this);
if (this->hWnd) {
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
}
}
cfg::ConfiguratorWindow::~ConfiguratorWindow() {
+1 -1
View File
@@ -6,7 +6,7 @@
name="spicecfg.exe"
type="win32"
/>
<description>SpiceTools Config</description>
<description>spice2x config</description>
<dependency>
<dependentAssembly>
<assemblyIdentity
+18 -3
View File
@@ -68,17 +68,32 @@ std::vector<std::string> Option::values_text() const {
return values;
}
int Option::value_int() const {
uint32_t Option::value_uint32() const {
if (this->definition.type != OptionType::Integer && this->definition.type != OptionType::Enum) {
log_fatal("option", "value_int() called on {}/{}", this->definition.title, this->definition.type);
log_fatal("option", "invalid call: value_uint32() called on {}/{}", this->definition.title, this->definition.type);
return 0;
}
char *p;
auto res = strtol(this->value.c_str(), &p, 10);
if (*p) {
log_fatal("option", "tried to convert {} to integer ({})", this->value, this->definition.title);
log_fatal("option", "failed to convert {} to unsigned integer (option: {})", this->value, this->definition.title);
return 0;
} else {
return res;
}
}
uint64_t Option::value_hex64() const {
if (this->definition.type != OptionType::Hex) {
log_fatal("option", "invalid call: value_hex() called on {}/{}", this->definition.title, this->definition.type);
return 0;
}
uint64_t affinity = 0;
try {
affinity = std::stoull(this->value.c_str(), nullptr, 16);
} catch (const std::exception &ex) {
log_fatal("option", "failed to parse {} as hexadecimal (option: {})", this->value, this->definition.title);
}
return affinity;
}
+3 -1
View File
@@ -9,6 +9,7 @@ enum class OptionType {
Text,
Integer,
Enum,
Hex,
};
struct OptionDefinition {
@@ -54,5 +55,6 @@ public:
const std::string &value_text() const;
std::vector<std::string> values() const;
std::vector<std::string> values_text() const;
int value_int() const;
uint32_t value_uint32() const;
uint64_t value_hex64() const;
};
+1
View File
@@ -4,3 +4,4 @@
#define IDR_LICENSES 131
#define IDR_PATCHES 132
#define IDR_README 133
#define IDR_DSEGFONT 134
+155 -64
View File
@@ -1,11 +1,12 @@
#include "screen_resize.h"
#include "external/rapidjson/document.h"
#include "external/rapidjson/writer.h"
#include "external/rapidjson/pointer.h"
#include "external/rapidjson/prettywriter.h"
#include "misc/eamuse.h"
#include "util/utils.h"
#include "util/fileutils.h"
using namespace rapidjson;
#include "hooks/graphics/graphics.h"
namespace cfg {
@@ -26,90 +27,180 @@ namespace cfg {
log_info("ScreenResize", "loading config");
std::string config = fileutils::text_read(this->config_path);
if (!config.empty()) {
if (config.empty()) {
return;
}
// parse document
Document doc;
rapidjson::Document doc;
doc.Parse(config.c_str());
// check parse error
auto error = doc.GetParseError();
if (error) {
log_warning("ScreenResize", "config parse error: {}", error);
return;
}
// verify root is a dict
if (doc.IsObject()) {
bool valid = true;
auto offset_x = doc.FindMember("offset_x");
if (offset_x == doc.MemberEnd() || !offset_x->value.IsInt()) {
log_warning("ScreenResize", "offset_x not found");
valid = false;
}
auto offset_y = doc.FindMember("offset_y");
if (offset_y == doc.MemberEnd() || !offset_y->value.IsInt()) {
log_warning("ScreenResize", "offset_y not found");
valid = false;
}
auto scale_x = doc.FindMember("scale_x");
if (scale_x == doc.MemberEnd() || !scale_x->value.IsDouble()) {
log_warning("ScreenResize", "scale_x not found");
valid = false;
}
auto scale_y = doc.FindMember("scale_y");
if (scale_y == doc.MemberEnd() || !scale_y->value.IsDouble()) {
log_warning("ScreenResize", "scale_y not found");
valid = false;
}
auto enable_screen_resize = doc.FindMember("enable_screen_resize");
if (enable_screen_resize == doc.MemberEnd() || !enable_screen_resize->value.IsBool()) {
log_warning("ScreenResize", "enable_screen_resize not found");
valid = false;
}
auto enable_linear_filter = doc.FindMember("enable_linear_filter");
if (enable_linear_filter == doc.MemberEnd() || !enable_linear_filter->value.IsBool()) {
log_warning("ScreenResize", "enable_linear_filter not found");
valid = false;
}
auto keep_aspect_ratio = doc.FindMember("keep_aspect_ratio");
if (keep_aspect_ratio == doc.MemberEnd() || !keep_aspect_ratio->value.IsBool()) {
log_warning("ScreenResize", "keep_aspect_ratio not found");
valid = false;
}
if (valid) {
this->offset_x = offset_x->value.GetInt();
this->offset_y = offset_y->value.GetInt();
this->scale_x = (float)scale_x->value.GetDouble();
this->scale_y = (float)scale_y->value.GetDouble();
this->enable_screen_resize = enable_screen_resize->value.GetBool();
this->enable_linear_filter = enable_linear_filter->value.GetBool();
this->keep_aspect_ratio = keep_aspect_ratio->value.GetBool();
}
} else {
if (!doc.IsObject()) {
log_warning("ScreenResize", "config not found");
return;
}
bool use_game_setting = false;
std::string root("/");
// try to find game-specific setting, if one exists
{
const auto game = rapidjson::Pointer("/sp2x_games/" + eamuse_get_game()).Get(doc);
if (game && game->IsObject()) {
use_game_setting = true;
root = "/sp2x_games/" + eamuse_get_game() + "/";
}
}
log_misc(
"ScreenResize",
"Loading fullscreen image settings. Game = {}, is_global = {}, JSON path: {}",
eamuse_get_game(),
use_game_setting,
root);
load_int_value(doc, root + "offset_x", this->offset_x);
load_int_value(doc, root + "offset_y", this->offset_y);
load_float_value(doc, root + "scale_x", this->scale_x);
load_float_value(doc, root + "scale_y", this->scale_y);
load_bool_value(doc, root + "enable_screen_resize", this->enable_screen_resize);
load_bool_value(doc, root + "enable_linear_filter", this->enable_linear_filter);
load_bool_value(doc, root + "keep_aspect_ratio", this->keep_aspect_ratio);
load_bool_value(doc, root + "centered", this->centered);
// windowed settings are always under game settings
root = "/sp2x_games/" + eamuse_get_game() + "/";
log_misc(
"ScreenResize",
"Loading window settings. Game = {}, JSON path: {}",
eamuse_get_game(),
root);
load_bool_value(doc, root + "w_always_on_top", this->window_always_on_top);
load_bool_value(doc, root + "w_enable_resize", this->enable_window_resize);
load_bool_value(doc, root + "w_keep_aspect_ratio", this->client_keep_aspect_ratio);
load_int_value(doc, root + "w_border_type", this->window_decoration);
load_uint32_value(doc, root + "w_width", this->client_width);
load_uint32_value(doc, root + "w_height", this->client_height);
load_int_value(doc, root + "w_offset_x", this->window_offset_x);
load_int_value(doc, root + "w_offset_y", this->window_offset_y);
}
bool ScreenResize::load_bool_value(rapidjson::Document& doc, std::string path, bool& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_warning("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsBool()) {
log_warning("ScreenResize", "{} is invalid type", path);
return false;
}
value = v->GetBool();
return true;
}
bool ScreenResize::load_int_value(rapidjson::Document& doc, std::string path, int& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_warning("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsInt()) {
log_warning("ScreenResize", "{} is invalid type", path);
return false;
}
value = v->GetInt();
return true;
}
bool ScreenResize::load_uint32_value(rapidjson::Document& doc, std::string path, uint32_t& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_warning("ScreenResize", "{} not found", path);
return false;
}
if (!v->IsUint()) {
log_warning("ScreenResize", "{} is invalid type", path);
return false;
}
value = v->GetUint();
return true;
}
bool ScreenResize::load_float_value(rapidjson::Document& doc, std::string path, float& value) {
const auto v = rapidjson::Pointer(path).Get(doc);
if (!v) {
log_warning("ScreenResize", "{} not found", path);
return false;
}
if (v->IsInt()) {
value = v->GetInt();
return true;
}
if (v->IsDouble()) {
value = v->GetDouble();
return true;
}
if (v->IsFloat()) {
value = v->GetFloat();
return true;
}
return false;
}
void ScreenResize::config_save() {
log_info("ScreenResize", "saving config");
// create document
Document doc;
rapidjson::Document doc;
std::string config = fileutils::text_read(this->config_path);
if (!config.empty()) {
doc.Parse(config.c_str());
log_misc("ScreenResize", "existing config file found");
}
if (!doc.IsObject()) {
log_misc("ScreenResize", "clearing out config file");
doc.SetObject();
auto &alloc = doc.GetAllocator();
}
doc.AddMember("offset_x", this->offset_x, alloc);
doc.AddMember("offset_y", this->offset_y, alloc);
doc.AddMember("scale_x", this->scale_x, alloc);
doc.AddMember("scale_y", this->scale_y, alloc);
doc.AddMember("enable_screen_resize", this->enable_screen_resize, alloc);
doc.AddMember("enable_linear_filter", this->enable_linear_filter, alloc);
doc.AddMember("keep_aspect_ratio", this->keep_aspect_ratio, alloc);
// always save under per-game settings
std::string root("/sp2x_games/" + eamuse_get_game() + "/");
log_misc(
"ScreenResize",
"Game = {}, JSON path = {}",
eamuse_get_game(),
root);
// full screen image settings
rapidjson::Pointer(root + "offset_x").Set(doc, this->offset_x);
rapidjson::Pointer(root + "offset_y").Set(doc, this->offset_y);
rapidjson::Pointer(root + "scale_x").Set(doc, this->scale_x);
rapidjson::Pointer(root + "scale_y").Set(doc, this->scale_y);
rapidjson::Pointer(root + "enable_screen_resize").Set(doc, this->enable_screen_resize);
rapidjson::Pointer(root + "enable_linear_filter").Set(doc, this->enable_linear_filter);
rapidjson::Pointer(root + "keep_aspect_ratio").Set(doc, this->keep_aspect_ratio);
rapidjson::Pointer(root + "centered").Set(doc, this->centered);
// windowed mode settings
rapidjson::Pointer(root + "w_always_on_top").Set(doc, this->window_always_on_top);
rapidjson::Pointer(root + "w_enable_resize").Set(doc, this->enable_window_resize);
rapidjson::Pointer(root + "w_keep_aspect_ratio").Set(doc, this->client_keep_aspect_ratio);
rapidjson::Pointer(root + "w_border_type").Set(doc, this->window_decoration);
rapidjson::Pointer(root + "w_width").Set(doc, this->client_width);
rapidjson::Pointer(root + "w_height").Set(doc, this->client_height);
rapidjson::Pointer(root + "w_offset_x").Set(doc, this->window_offset_x);
rapidjson::Pointer(root + "w_offset_y").Set(doc, this->window_offset_y);
// build JSON
StringBuffer buffer;
Writer<StringBuffer> writer(buffer);
rapidjson::StringBuffer buffer;
rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
doc.Accept(writer);
// save to file
+37 -2
View File
@@ -2,25 +2,60 @@
#include <memory>
#include <string>
#include "external/rapidjson/document.h"
namespace cfg {
enum WindowDecorationMode {
Default = 0,
Borderless = 1,
ResizableFrame = 2
};
class ScreenResize {
private:
std::string config_path;
// bool config_dirty = false;
bool load_bool_value(rapidjson::Document& doc, std::string path, bool& value);
bool load_int_value(rapidjson::Document& doc, std::string path, int& value);
bool load_uint32_value(rapidjson::Document& doc, std::string path, uint32_t& value);
bool load_float_value(rapidjson::Document& doc, std::string path, float& value);
public:
ScreenResize();
~ScreenResize();
// full screen (directx) image settings
int offset_x = 0;
int offset_y = 0;
float scale_x = 1.0;
float scale_y = 1.0;
bool enable_screen_resize = false;
bool enable_linear_filter = false;
bool keep_aspect_ratio = false;
bool enable_linear_filter = true;
bool keep_aspect_ratio = true;
bool centered = true;
// windowed mode sizing
// Windows terminology:
// window = rectangle including the frame
// client = just the content area without frames.
bool window_always_on_top = false;
bool client_keep_aspect_ratio = true;
bool enable_window_resize = false;
int window_decoration = 0; // enum type WindowDecorationMode
uint32_t client_width = 0;
uint32_t client_height = 0;
int32_t window_offset_x = 0;
int32_t window_offset_y = 0;
// these are not saved by config, but used by window management
uint32_t init_client_width = 0;
uint32_t init_client_height = 0;
float init_client_aspect_ratio = 1.f;
uint32_t init_window_style = 0;
uint32_t window_deco_width = 0;
uint32_t window_deco_height = 0;
void config_load();
void config_save();
+79
View File
@@ -1,3 +1,82 @@
06/08/2023 [spice2x]
Improved touch support for Jubeat and Reflec Beat.
04/23/2023 [spice2x]
Update -iidxsounddevice to automatically pick a value (wasapi in most cases)
04/21/2023 [spice2x]
Fix spicecfg not launching in some specific cases
04/20/2023 [spice2x]
Update font for IIDX segment display
New option: auto show FPS window
04/16/2023 [spice2x] (beta)
New feature: window resize. Added new options to change window size and
position on launch. Screen Resize window (F11) also updated with new
controls
Screen Resize settings are now per-game
Misc bug fixes
04/09/2023 [spice2x] (beta)
Add IIDX LED ticker (segment display) as subscreen overlay for LDJ
Add I/O panel window for all games, special support for IIDX, DDR, GFDM
Add options to automatically show certain windows on game launch
04/03/2023 [spice2x] (beta)
Move/resize for IIDX/SDVX subscreen overlay window
New options to control subscreen overlay window
Turn common config mistakes into fatal error messages
Various bug fixes for touch handling
Revert "pop'n launching at desktop refresh rate" hack from 03/30/2023
03/30/2023 [spice2x]
New option: IIDX Force Definition Type HD (720p)
New option: process priority
Fix pop'n launching at desktop refresh rate
Lower logger thread priority to reduce stutters
Misc bug fixes
03/25/2023 [spice2x]
Screen Resize UI update (F11)
FPS UI update (F12)
New option: processor affinity
Misc bug fixes
03/20/2023 [spice2x]
ASIO device override for SDVX (experimental)
Fix subscreen overlay for SDVX in Valkyrie mode using BI2X I/O
Update text for touch and audio config options
UI tweaks and minor bug fixes
03/17/2023 [spice2x]
Disable UPX (file sizes will be larger)
Fix regression from 2022 that prevented early patches
from being loaded properly (such as audio hex edits)
New option: HID lighting brightness
New option: display auto-rotate direction
New option: custom sleep duration
03/15/2023 [spice2x]
Improve IIDX TT+/- handling (allow alternate key bind)
Fix SpiceCompanion touch targets for TDJ FHD
Config UI tweaks
03/13/2023 [spice2x]
Fix TDJ FHD launching at wrong refresh rate
New IIDX/SDVX options for TT/knob sensitivity when using digital input
03/12/2023 [spice2x]
Fix mouse input for SDVX EG subscreen overlay
New unified layout for Options tab
Allow multiple DLLs in Inject Hook field
03/11/2023 [spice2x]
Beginning of spice2x fork, based on another fork
Ensure TDJ mode runs at 1080p@120Hz for newer games
Fix IIDX subscreen overlay in FHD mode
Gracefully deal with bad ea3-config.xml
04/02/2022 - Add lua scripting
Add button edit dialog
Add button debounce/invert settings
+95
View File
@@ -0,0 +1,95 @@
Copyright (c) 2017, keshikan (http://www.keshikan.net),
with Reserved Font Name "DSEG".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
File diff suppressed because one or more lines are too long
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
The MIT License (MIT)
Copyright (c) 2014-2019 Omar Cornut
Copyright (c) 2014-2023 Omar Cornut
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
+6 -9
View File
@@ -30,11 +30,11 @@
//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//#define IMGUI_DISABLE_OBSOLETE_KEYIO // 1.87: disable legacy io.KeyMap[]+io.KeysDown[] in favor io.AddKeyEvent(). This will be folded into IMGUI_DISABLE_OBSOLETE_FUNCTIONS in a few versions.
//---- Disable all of Dear ImGui or don't implement standard windows.
// It is very strongly recommended to NOT disable the demo windows during development. Please read comments in imgui_demo.cpp.
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
//#define IMGUI_DISABLE // Disable everything: all headers and source files will be empty.
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty. Not recommended.
//#define IMGUI_DISABLE_METRICS_WINDOW // Disable metrics/debugger and other debug tools: ShowMetricsWindow() and ShowStackToolWindow() will be empty.
//#define IMGUI_DISABLE_DEMO_WINDOWS // Disable demo windows: ShowDemoWindow()/ShowStyleEditor() will be empty.
//#define IMGUI_DISABLE_DEBUG_TOOLS // Disable metrics/debugger and other debug tools: ShowMetricsWindow(), ShowDebugLogWindow() and ShowStackToolWindow() will be empty (this was called IMGUI_DISABLE_METRICS_WINDOW before 1.88).
//---- Don't implement some functions to reduce linkage requirements.
//#define IMGUI_DISABLE_WIN32_DEFAULT_CLIPBOARD_FUNCTIONS // [Win32] Don't implement default clipboard handler. Won't use and link with OpenClipboard/GetClipboardData/CloseClipboard etc. (user32.lib/.a, kernel32.lib/.a)
@@ -93,6 +93,8 @@
constexpr ImVec4(const MyVec4& f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \
operator MyVec4() const { return MyVec4(x,y,z,w); }
*/
//---- ...Or use Dear ImGui's own very basic math operators.
//#define IMGUI_DEFINE_MATH_OPERATORS
//---- Use 32-bit vertex indices (default is 16-bit) is one way to allow large meshes with more than 64K vertices.
// Your renderer backend will need to support it (most example renderer backends support both 16/32-bit indices).
@@ -111,11 +113,6 @@
//#define IM_DEBUG_BREAK IM_ASSERT(0)
//#define IM_DEBUG_BREAK __debugbreak()
//---- Debug Tools: Have the Item Picker break in the ItemAdd() function instead of ItemHoverable(),
// (which comes earlier in the code, will catch a few extra items, allow picking items other than Hovered one.)
// This adds a small runtime cost which is why it is not enabled by default.
//#define IMGUI_DEBUG_TOOL_ITEM_PICKER_EX
//---- Debug Tools: Enable slower asserts
//#define IMGUI_DEBUG_PARANOID
+3236 -1676
View File
File diff suppressed because it is too large Load Diff
+367 -270
View File
File diff suppressed because it is too large Load Diff
+496 -382
View File
File diff suppressed because it is too large Load Diff
+112 -101
View File
@@ -1,4 +1,4 @@
// dear imgui, v1.88 WIP
// dear imgui, v1.89.5 WIP
// (drawing and font code)
/*
@@ -26,38 +26,24 @@ Index of this file:
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "imgui.h"
#ifndef IMGUI_DISABLE
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_internal.h"
#ifdef IMGUI_ENABLE_FREETYPE
#include "misc/freetype/imgui_freetype.h"
#endif
#include <stdio.h> // vsnprintf, sscanf, printf
#if !defined(alloca)
#if defined(__GLIBC__) || defined(__sun) || defined(__APPLE__) || defined(__NEWLIB__)
#include <alloca.h> // alloca (glibc uses <alloca.h>. Note that Cygwin may have _WIN32 defined, so the order matters here)
#elif defined(_WIN32)
#include <malloc.h> // alloca
#if !defined(alloca)
#define alloca _alloca // for clang with MS Codegen
#endif
#else
#include <stdlib.h> // alloca
#endif
#endif
// Visual Studio warnings
#ifdef _MSC_VER
#pragma warning (disable: 4127) // condition expression is constant
#pragma warning (disable: 4505) // unreferenced local function has been removed (stb stuff)
#pragma warning (disable: 4996) // 'This function or variable may be unsafe': strcpy, strdup, sprintf, vsnprintf, sscanf, fopen
#pragma warning (disable: 6255) // [Static Analyzer] _alloca indicates failure by raising a stack overflow exception. Consider using _malloca instead.
#pragma warning (disable: 26451) // [Static Analyzer] Arithmetic overflow : Using operator 'xxx' on a 4 byte value and then casting the result to a 8 byte value. Cast the value to the wider type before calling operator 'xxx' to avoid overflow(io.2).
#pragma warning (disable: 26812) // [Static Analyzer] The enum type 'xxx' is unscoped. Prefer 'enum class' over 'enum' (Enum.3). [MSVC Static Analyzer)
#endif
@@ -67,9 +53,6 @@ Index of this file:
#if __has_warning("-Wunknown-warning-option")
#pragma clang diagnostic ignored "-Wunknown-warning-option" // warning: unknown warning group 'xxx' // not all warnings are known by all Clang versions and they tend to be rename-happy.. so ignoring warnings triggers new warnings on some configuration. Great!
#endif
#if __has_warning("-Walloca")
#pragma clang diagnostic ignored "-Walloca" // warning: use of function '__builtin_alloca' is discouraged
#endif
#pragma clang diagnostic ignored "-Wunknown-pragmas" // warning: unknown warning group 'xxx'
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wfloat-equal" // warning: comparing floating point with == or != is unsafe // storing and comparing against same constants ok.
@@ -399,7 +382,7 @@ void ImDrawListSharedData::SetCircleTessellationMaxError(float max_error)
for (int i = 0; i < IM_ARRAYSIZE(CircleSegmentCounts); i++)
{
const float radius = (float)i;
CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : 0);
CircleSegmentCounts[i] = (ImU8)((i > 0) ? IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(radius, CircleSegmentMaxError) : IM_DRAWLIST_ARCFAST_SAMPLE_MAX);
}
ArcFastRadiusCutoff = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(IM_DRAWLIST_ARCFAST_SAMPLE_MAX, CircleSegmentMaxError);
}
@@ -471,12 +454,14 @@ void ImDrawList::AddDrawCmd()
// Note that this leaves the ImDrawList in a state unfit for further commands, as most code assume that CmdBuffer.Size > 0 && CmdBuffer.back().UserCallback == NULL
void ImDrawList::_PopUnusedDrawCmd()
{
if (CmdBuffer.Size == 0)
return;
while (CmdBuffer.Size > 0)
{
ImDrawCmd* curr_cmd = &CmdBuffer.Data[CmdBuffer.Size - 1];
if (curr_cmd->ElemCount == 0 && curr_cmd->UserCallback == NULL)
if (curr_cmd->ElemCount != 0 || curr_cmd->UserCallback != NULL)
return;// break;
CmdBuffer.pop_back();
}
}
void ImDrawList::AddCallback(ImDrawCallback callback, void* callback_data)
{
@@ -728,7 +713,7 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c
// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds.
void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags, float thickness)
{
if (points_count < 2)
if (points_count < 2 || (col & IM_COL32_A_MASK) == 0)
return;
const bool closed = (flags & ImDrawFlags_Closed) != 0;
@@ -761,7 +746,8 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32
// Temporary buffer
// The first <points_count> items are normals at each line point, then after that there are either 2 or 4 temp points for each line point
ImVec2* temp_normals = (ImVec2*)alloca(points_count * ((use_texture || !thick_line) ? 3 : 5) * sizeof(ImVec2)); //-V630
_Data->TempBuffer.reserve_discard(points_count * ((use_texture || !thick_line) ? 3 : 5));
ImVec2* temp_normals = _Data->TempBuffer.Data;
ImVec2* temp_points = temp_normals + points_count;
// Calculate normals (tangents) for each line segment
@@ -985,7 +971,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32
// - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing.
void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col)
{
if (points_count < 3)
if (points_count < 3 || (col & IM_COL32_A_MASK) == 0)
return;
const ImVec2 uv = _Data->TexUvWhitePixel;
@@ -1009,7 +995,8 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun
}
// Compute normals
ImVec2* temp_normals = (ImVec2*)alloca(points_count * sizeof(ImVec2)); //-V630
_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];
@@ -1066,7 +1053,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun
void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step)
{
if (radius <= 0.0f)
if (radius < 0.5f)
{
_Path.push_back(center);
return;
@@ -1158,7 +1145,7 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_
void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments)
{
if (radius <= 0.0f)
if (radius < 0.5f)
{
_Path.push_back(center);
return;
@@ -1177,7 +1164,7 @@ void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, fl
// 0: East, 3: South, 6: West, 9: North, 12: East
void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12)
{
if (radius <= 0.0f)
if (radius < 0.5f)
{
_Path.push_back(center);
return;
@@ -1187,7 +1174,7 @@ void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_
void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments)
{
if (radius <= 0.0f)
if (radius < 0.5f)
{
_Path.push_back(center);
return;
@@ -1303,6 +1290,7 @@ void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, cons
ImVec2 p1 = _Path.back();
if (num_segments == 0)
{
IM_ASSERT(_Data->CurveTessellationTol > 0.0f);
PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); // Auto-tessellated
}
else
@@ -1318,6 +1306,7 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3,
ImVec2 p1 = _Path.back();
if (num_segments == 0)
{
IM_ASSERT(_Data->CurveTessellationTol > 0.0f);
PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0);// Auto-tessellated
}
else
@@ -1332,6 +1321,7 @@ IM_STATIC_ASSERT(ImDrawFlags_RoundCornersTopLeft == (1 << 4));
static inline ImDrawFlags FixRectCornerFlags(ImDrawFlags flags)
{
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
// Obsoleted in 1.82 (from February 2021)
// Legacy Support for hard coded ~0 (used to be a suggested equivalent to ImDrawCornerFlags_All)
// ~0 --> ImDrawFlags_RoundCornersAll or 0
if (flags == ~0)
@@ -1368,7 +1358,7 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr
rounding = ImMin(rounding, ImFabs(b.x - a.x) * ( ((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f ) - 1.0f);
rounding = ImMin(rounding, ImFabs(b.y - a.y) * ( ((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f ) - 1.0f);
if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
{
PathLineTo(a);
PathLineTo(ImVec2(b.x, a.y));
@@ -1414,7 +1404,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c
{
if ((col & IM_COL32_A_MASK) == 0)
return;
if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
{
PrimReserve(6, 4);
PrimRect(p_min, p_max, col);
@@ -1490,7 +1480,7 @@ void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImV
void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness)
{
if ((col & IM_COL32_A_MASK) == 0 || radius <= 0.0f)
if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f)
return;
if (num_segments <= 0)
@@ -1514,7 +1504,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu
void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments)
{
if ((col & IM_COL32_A_MASK) == 0 || radius <= 0.0f)
if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f)
return;
if (num_segments <= 0)
@@ -1654,7 +1644,7 @@ void ImDrawList::AddImageRounded(ImTextureID user_texture_id, const ImVec2& p_mi
return;
flags = FixRectCornerFlags(flags);
if (rounding <= 0.0f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone)
{
AddImage(user_texture_id, p_min, p_max, uv_min, uv_max, col);
return;
@@ -2306,10 +2296,11 @@ void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], fl
void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride)
{
IM_ASSERT_PARANOID(w <= stride);
unsigned char* data = pixels + x + y * stride;
for (int j = h; j > 0; j--, data += stride)
for (int i = 0; i < w; i++)
data[i] = table[data[i]];
for (int j = h; j > 0; j--, data += stride - w)
for (int i = w; i > 0; i--, data++)
*data = table[*data];
}
#ifdef IMGUI_ENABLE_STB_TRUETYPE
@@ -2326,7 +2317,7 @@ struct ImFontBuildSrcData
int GlyphsHighest; // Highest requested codepoint
int GlyphsCount; // Glyph count (excluding missing glyphs and glyphs already set by an earlier source font)
ImBitVector GlyphsSet; // Glyph bit map (random access, 1-bit per codepoint. This will be a maximum of 8KB)
ImVector<int> GlyphsList; // Glyph codepoints list (flattened version of GlyphsMap)
ImVector<int> GlyphsList; // Glyph codepoints list (flattened version of GlyphsSet)
};
// Temporary data for one destination ImFont* (multiple source fonts can be merged into one destination ImFont)
@@ -2398,7 +2389,12 @@ static bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas)
ImFontBuildDstData& dst_tmp = dst_tmp_array[src_tmp.DstIndex];
src_tmp.SrcRanges = cfg.GlyphRanges ? cfg.GlyphRanges : atlas->GetGlyphRangesDefault();
for (const ImWchar* src_range = src_tmp.SrcRanges; src_range[0] && src_range[1]; src_range += 2)
{
// Check for valid range. This may also help detect *some* dangling pointers, because a common
// user error is to setup ImFontConfig::GlyphRanges with a pointer to data that isn't persistent.
IM_ASSERT(src_range[0] <= src_range[1]);
src_tmp.GlyphsHighest = ImMax(src_tmp.GlyphsHighest, (int)src_range[1]);
}
dst_tmp.SrcCount++;
dst_tmp.GlyphsHighest = ImMax(dst_tmp.GlyphsHighest, src_tmp.GlyphsHighest);
}
@@ -2826,6 +2822,17 @@ const ImWchar* ImFontAtlas::GetGlyphRangesDefault()
return &ranges[0];
}
const ImWchar* ImFontAtlas::GetGlyphRangesGreek()
{
static const ImWchar ranges[] =
{
0x0020, 0x00FF, // Basic Latin + Latin Supplement
0x0370, 0x03FF, // Greek and Coptic
0,
};
return &ranges[0];
}
const ImWchar* ImFontAtlas::GetGlyphRangesKorean()
{
static const ImWchar ranges[] =
@@ -2942,19 +2949,19 @@ const ImWchar* ImFontAtlas::GetGlyphRangesJapanese()
// 2999 ideograms code points for Japanese
// - 2136 Joyo (meaning "for regular use" or "for common use") Kanji code points
// - 863 Jinmeiyo (meaning "for personal name") Kanji code points
// - Sourced from the character information database of the Information-technology Promotion Agency, Japan
// - https://mojikiban.ipa.go.jp/mji/
// - Available under the terms of the Creative Commons Attribution-ShareAlike 2.1 Japan (CC BY-SA 2.1 JP).
// - https://creativecommons.org/licenses/by-sa/2.1/jp/deed.en
// - https://creativecommons.org/licenses/by-sa/2.1/jp/legalcode
// - Sourced from official information provided by the government agencies of Japan:
// - List of Joyo Kanji by the Agency for Cultural Affairs
// - https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kijun/naikaku/kanji/
// - List of Jinmeiyo Kanji by the Ministry of Justice
// - http://www.moj.go.jp/MINJI/minji86.html
// - Available under the terms of the Creative Commons Attribution 4.0 International (CC BY 4.0).
// - https://creativecommons.org/licenses/by/4.0/legalcode
// - You can generate this code by the script at:
// - https://github.com/vaiorabbit/everyday_use_kanji
// - References:
// - List of Joyo Kanji
// - (Official list by the Agency for Cultural Affairs) https://www.bunka.go.jp/kokugo_nihongo/sisaku/joho/joho/kakuki/14/tosin02/index.html
// - (Wikipedia) https://en.wikipedia.org/wiki/List_of_j%C5%8Dy%C5%8D_kanji
// - List of Jinmeiyo Kanji
// - (Official list by the Ministry of Justice) http://www.moj.go.jp/MINJI/minji86.html
// - (Wikipedia) https://en.wikipedia.org/wiki/Jinmeiy%C5%8D_kanji
// - Missing 1 Joyo Kanji: U+20B9F (Kun'yomi: Shikaru, On'yomi: Shitsu,shichi), see https://github.com/ocornut/imgui/pull/3627 for details.
// You can use ImFontGlyphRangesBuilder to create your own ranges derived from this, by merging existing ranges or adding new characters.
@@ -3117,7 +3124,8 @@ ImFont::ImFont()
FallbackAdvanceX = 0.0f;
FallbackChar = (ImWchar)-1;
EllipsisChar = (ImWchar)-1;
DotChar = (ImWchar)-1;
EllipsisWidth = EllipsisCharStep = 0.0f;
EllipsisCharCount = 0;
FallbackGlyph = NULL;
ContainerAtlas = NULL;
ConfigData = NULL;
@@ -3205,8 +3213,20 @@ void ImFont::BuildLookupTable()
const ImWchar dots_chars[] = { (ImWchar)'.', (ImWchar)0xFF0E };
if (EllipsisChar == (ImWchar)-1)
EllipsisChar = FindFirstExistingGlyph(this, ellipsis_chars, IM_ARRAYSIZE(ellipsis_chars));
if (DotChar == (ImWchar)-1)
DotChar = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars));
const ImWchar dot_char = FindFirstExistingGlyph(this, dots_chars, IM_ARRAYSIZE(dots_chars));
if (EllipsisChar != (ImWchar)-1)
{
EllipsisCharCount = 1;
EllipsisWidth = EllipsisCharStep = FindGlyph(EllipsisChar)->X1;
}
else if (dot_char != (ImWchar)-1)
{
const ImFontGlyph* glyph = FindGlyph(dot_char);
EllipsisChar = dot_char;
EllipsisCharCount = 3;
EllipsisCharStep = (glyph->X1 - glyph->X0) + 1.0f;
EllipsisWidth = EllipsisCharStep * 3.0f - 1.0f;
}
// Setup fallback character
const ImWchar fallback_chars[] = { (ImWchar)IM_UNICODE_CODEPOINT_INVALID, (ImWchar)'?', (ImWchar)' ' };
@@ -3338,11 +3358,21 @@ const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const
return &Glyphs.Data[i];
}
// Wrapping skips upcoming blanks
static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end)
{
while (text < text_end && ImCharIsBlankA(*text))
text++;
if (*text == '\n')
text++;
return text;
}
// Simple word-wrapping for English, not full-featured. Please submit failing cases!
// This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end.
// FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.)
const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const
{
// Simple word-wrapping for English, not full-featured. Please submit failing cases!
// FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.)
// For references, possible wrap point marked with ^
// "aaa bbb, ccc,ddd. eee fff. ggg!"
// ^ ^ ^ ^ ^__ ^ ^
@@ -3354,7 +3384,6 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c
// Cut words that cannot possibly fit within one line.
// e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish"
float line_width = 0.0f;
float word_width = 0.0f;
float blank_width = 0.0f;
@@ -3365,6 +3394,7 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c
bool inside_word = true;
const char* s = text;
IM_ASSERT(text_end != NULL);
while (s < text_end)
{
unsigned int c = (unsigned int)*s;
@@ -3373,8 +3403,6 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c
next_s = s + 1;
else
next_s = s + ImTextCharFromUtf8(&c, s, text_end);
if (c == 0)
break;
if (c < 32)
{
@@ -3434,6 +3462,10 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c
s = next_s;
}
// Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.
// +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol).
if (s == text && text < text_end)
return s + 1;
return s;
}
@@ -3458,11 +3490,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons
{
// Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.
if (!word_wrap_eol)
{
word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width);
if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.
word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below
}
if (s >= word_wrap_eol)
{
@@ -3471,13 +3499,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons
text_size.y += line_height;
line_width = 0.0f;
word_wrap_eol = NULL;
// Wrapping skips upcoming blanks
while (s < text_end)
{
const char c = *s;
if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; }
}
s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks
continue;
}
}
@@ -3486,15 +3508,9 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons
const char* prev_s = s;
unsigned int c = (unsigned int)*s;
if (c < 0x80)
{
s += 1;
}
else
{
s += ImTextCharFromUtf8(&c, s, text_end);
if (c == 0) // Malformed UTF-8?
break;
}
if (c < 32)
{
@@ -3562,15 +3578,25 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
const float scale = size / FontSize;
const float line_height = FontSize * scale;
const bool word_wrap_enabled = (wrap_width > 0.0f);
const char* word_wrap_eol = NULL;
// Fast-forward to first visible line
const char* s = text_begin;
if (y + line_height < clip_rect.y && !word_wrap_enabled)
if (y + line_height < clip_rect.y)
while (y + line_height < clip_rect.y && s < text_end)
{
s = (const char*)memchr(s, '\n', text_end - s);
s = s ? s + 1 : text_end;
const char* line_end = (const char*)memchr(s, '\n', text_end - s);
if (word_wrap_enabled)
{
// FIXME-OPT: This is not optimal as do first do a search for \n before calling CalcWordWrapPositionA().
// If the specs for CalcWordWrapPositionA() were reworked to optionally return on \n we could combine both.
// However it is still better than nothing performing the fast-forward!
s = CalcWordWrapPositionA(scale, s, line_end ? line_end : text_end, wrap_width);
s = CalcWordWrapNextLineStartA(s, text_end);
}
else
{
s = line_end ? line_end + 1 : text_end;
}
y += line_height;
}
@@ -3596,12 +3622,12 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
const int idx_count_max = (int)(text_end - s) * 6;
const int idx_expected_size = draw_list->IdxBuffer.Size + idx_count_max;
draw_list->PrimReserve(idx_count_max, vtx_count_max);
ImDrawVert* vtx_write = draw_list->_VtxWritePtr;
ImDrawIdx* idx_write = draw_list->_IdxWritePtr;
unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx;
unsigned int vtx_index = draw_list->_VtxCurrentIdx;
const ImU32 col_untinted = col | ~IM_COL32_A_MASK;
const char* word_wrap_eol = NULL;
while (s < text_end)
{
@@ -3609,24 +3635,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
{
// Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature.
if (!word_wrap_eol)
{
word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - start_x));
if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity.
word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below
}
if (s >= word_wrap_eol)
{
x = start_x;
y += line_height;
word_wrap_eol = NULL;
// Wrapping skips upcoming blanks
while (s < text_end)
{
const char c = *s;
if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; }
}
s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks
continue;
}
}
@@ -3634,15 +3650,9 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
// Decode and advance source
unsigned int c = (unsigned int)*s;
if (c < 0x80)
{
s += 1;
}
else
{
s += ImTextCharFromUtf8(&c, s, text_end);
if (c == 0) // Malformed UTF-8?
break;
}
if (c < 32)
{
@@ -3713,14 +3723,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
// We are NOT calling PrimRectUV() here because non-inlined causes too much overhead in a debug builds. Inlined here:
{
idx_write[0] = (ImDrawIdx)(vtx_current_idx); idx_write[1] = (ImDrawIdx)(vtx_current_idx+1); idx_write[2] = (ImDrawIdx)(vtx_current_idx+2);
idx_write[3] = (ImDrawIdx)(vtx_current_idx); idx_write[4] = (ImDrawIdx)(vtx_current_idx+2); idx_write[5] = (ImDrawIdx)(vtx_current_idx+3);
vtx_write[0].pos.x = x1; vtx_write[0].pos.y = y1; vtx_write[0].col = glyph_col; vtx_write[0].uv.x = u1; vtx_write[0].uv.y = v1;
vtx_write[1].pos.x = x2; vtx_write[1].pos.y = y1; vtx_write[1].col = glyph_col; vtx_write[1].uv.x = u2; vtx_write[1].uv.y = v1;
vtx_write[2].pos.x = x2; vtx_write[2].pos.y = y2; vtx_write[2].col = glyph_col; vtx_write[2].uv.x = u2; vtx_write[2].uv.y = v2;
vtx_write[3].pos.x = x1; vtx_write[3].pos.y = y2; vtx_write[3].col = glyph_col; vtx_write[3].uv.x = u1; vtx_write[3].uv.y = v2;
idx_write[0] = (ImDrawIdx)(vtx_index); idx_write[1] = (ImDrawIdx)(vtx_index + 1); idx_write[2] = (ImDrawIdx)(vtx_index + 2);
idx_write[3] = (ImDrawIdx)(vtx_index); idx_write[4] = (ImDrawIdx)(vtx_index + 2); idx_write[5] = (ImDrawIdx)(vtx_index + 3);
vtx_write += 4;
vtx_current_idx += 4;
vtx_index += 4;
idx_write += 6;
}
}
@@ -3734,7 +3744,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im
draw_list->CmdBuffer[draw_list->CmdBuffer.Size - 1].ElemCount -= (idx_expected_size - draw_list->IdxBuffer.Size);
draw_list->_VtxWritePtr = vtx_write;
draw_list->_IdxWritePtr = idx_write;
draw_list->_VtxCurrentIdx = vtx_current_idx;
draw_list->_VtxCurrentIdx = vtx_index;
}
//-----------------------------------------------------------------------------
@@ -3787,6 +3797,7 @@ void ImGui::RenderArrow(ImDrawList* draw_list, ImVec2 pos, ImU32 col, ImGuiDir d
void ImGui::RenderBullet(ImDrawList* draw_list, ImVec2 pos, ImU32 col)
{
// FIXME-OPT: This should be baked in font.
draw_list->AddCircleFilled(pos, draw_list->_Data->FontSize * 0.20f, col, 8);
}
+200 -36
View File
@@ -3,11 +3,13 @@
#include <algorithm>
#include <array>
#include <cassert>
#include <cctype>
#include <cstring>
#include <filesystem>
#include <memory>
#include <set>
#include <string>
#include <string_view>
#include <vector>
#ifndef IMGUI_VERSION
@@ -41,6 +43,10 @@ namespace ImGui
FileBrowser &operator=(const FileBrowser &copyFrom);
// set the window position (in pixels)
// default is centered
void SetWindowPos(int posx, int posy) noexcept;
// set the window size (in pixels)
// default is (700, 450)
void SetWindowSize(int width, int height) noexcept;
@@ -91,23 +97,36 @@ namespace ImGui
// default value is 0 (the first type filter)
void SetCurrentTypeFilterIndex(int index);
// when ImGuiFileBrowserFlags_EnterNewFilename is set
// this function will pre-fill the input dialog with a filename.
void SetInputName(std::string_view input);
private:
template <class Functor>
struct ScopeGuard
{
ScopeGuard(Functor&& t) : func(std::move(t)) { }
~ScopeGuard()
{
func();
}
~ScopeGuard() { func(); }
private:
Functor func;
};
struct FileRecord
{
bool isDir = false;
std::filesystem::path name;
std::string showName;
std::filesystem::path extension;
};
static std::string ToLower(const std::string &s);
void UpdateFileRecords();
void SetPwdUncatched(const std::filesystem::path &pwd);
bool IsExtensionMatched(const std::filesystem::path &extension) const;
@@ -125,6 +144,8 @@ namespace ImGui
int width_;
int height_;
int posX_;
int posY_;
ImGuiFileBrowserFlags flags_;
std::string title_;
@@ -134,23 +155,17 @@ namespace ImGui
bool closeFlag_;
bool isOpened_;
bool ok_;
bool posIsSet_;
std::string statusStr_;
std::vector<std::string> typeFilters_;
int typeFilterIndex_;
unsigned int typeFilterIndex_;
bool hasAllFilter_;
std::filesystem::path pwd_;
std::set<std::filesystem::path> selectedFilenames_;
struct FileRecord
{
bool isDir = false;
std::filesystem::path name;
std::string showName;
std::filesystem::path extension;
};
std::vector<FileRecord> fileRecords_;
// IMPROVE: truncate when selectedFilename_.length() > inputNameBuf_.size() - 1
@@ -167,20 +182,20 @@ namespace ImGui
} // namespace ImGui
inline ImGui::FileBrowser::FileBrowser(ImGuiFileBrowserFlags flags)
: width_(700), height_(450), flags_(flags),
openFlag_(false), closeFlag_(false), isOpened_(false), ok_(false),
: width_(700), height_(450), posX_(0), posY_(0), flags_(flags),
openFlag_(false), closeFlag_(false), isOpened_(false), ok_(false), posIsSet_(false),
inputNameBuf_(std::make_unique<std::array<char, INPUT_NAME_BUF_SIZE>>())
{
if(flags_ & ImGuiFileBrowserFlags_CreateNewDir)
newDirNameBuf_ = std::make_unique<
std::array<char, INPUT_NAME_BUF_SIZE>>();
{
newDirNameBuf_ =
std::make_unique<std::array<char, INPUT_NAME_BUF_SIZE>>();
}
inputNameBuf_->front() = '\0';
inputNameBuf_->back() = '\0';
SetTitle("File Browser");
try {
SetTitle("file browser");
SetPwd(std::filesystem::current_path());
} catch (std::filesystem::filesystem_error&) {}
typeFilters_.clear();
typeFilterIndex_ = 0;
@@ -203,6 +218,9 @@ inline ImGui::FileBrowser &ImGui::FileBrowser::operator=(
width_ = copyFrom.width_;
height_ = copyFrom.height_;
posX_ = copyFrom.posX_;
posY_ = copyFrom.posY_;
flags_ = copyFrom.flags_;
SetTitle(copyFrom.title_);
@@ -210,6 +228,7 @@ inline ImGui::FileBrowser &ImGui::FileBrowser::operator=(
closeFlag_ = copyFrom.closeFlag_;
isOpened_ = copyFrom.isOpened_;
ok_ = copyFrom.ok_;
posIsSet_ = copyFrom.posIsSet_;
statusStr_ = "";
@@ -224,6 +243,7 @@ inline ImGui::FileBrowser &ImGui::FileBrowser::operator=(
*inputNameBuf_ = *copyFrom.inputNameBuf_;
openNewDirLabel_ = copyFrom.openNewDirLabel_;
if(flags_ & ImGuiFileBrowserFlags_CreateNewDir)
{
newDirNameBuf_ = std::make_unique<
@@ -231,9 +251,20 @@ inline ImGui::FileBrowser &ImGui::FileBrowser::operator=(
*newDirNameBuf_ = *copyFrom.newDirNameBuf_;
}
#ifdef _WIN32
drives_ = copyFrom.drives_;
#endif
return *this;
}
inline void ImGui::FileBrowser::SetWindowPos(int posx, int posy) noexcept
{
posX_ = posx;
posY_ = posy;
posIsSet_ = true;
}
inline void ImGui::FileBrowser::SetWindowSize(int width, int height) noexcept
{
assert(width > 0 && height > 0);
@@ -253,6 +284,7 @@ inline void ImGui::FileBrowser::SetTitle(std::string title)
inline void ImGui::FileBrowser::Open()
{
ClearSelected();
UpdateFileRecords();
statusStr_ = std::string();
openFlag_ = true;
closeFlag_ = false;
@@ -282,18 +314,27 @@ inline void ImGui::FileBrowser::Display()
});
if(openFlag_)
{
OpenPopup(openLabel_.c_str());
}
isOpened_ = false;
// open the popup window
if(openFlag_ && (flags_ & ImGuiFileBrowserFlags_NoModal))
{
if (posIsSet_)
SetNextWindowPos(
ImVec2(static_cast<float>(posX_), static_cast<float>(posY_)));
SetNextWindowSize(
ImVec2(static_cast<float>(width_), static_cast<float>(height_)));
}
else
{
if (posIsSet_)
SetNextWindowPos(
ImVec2(static_cast<float>(posX_), static_cast<float>(posY_)),
ImGuiCond_FirstUseEver);
SetNextWindowSize(
ImVec2(static_cast<float>(width_), static_cast<float>(height_)),
ImGuiCond_FirstUseEver);
@@ -301,14 +342,17 @@ inline void ImGui::FileBrowser::Display()
if(flags_ & ImGuiFileBrowserFlags_NoModal)
{
if(!BeginPopup(openLabel_.c_str()))
{
return;
}
}
else if(!BeginPopupModal(openLabel_.c_str(), nullptr,
flags_ & ImGuiFileBrowserFlags_NoTitleBar ?
ImGuiWindowFlags_NoTitleBar : 0))
{
return;
}
isOpened_ = true;
ScopeGuard endPopup([] { EndPopup(); });
@@ -322,13 +366,18 @@ inline void ImGui::FileBrowser::Display()
if(BeginCombo("##select_drive", driveStr))
{
ScopeGuard guard([&] { EndCombo(); });
for(int i = 0; i < 26; ++i)
{
if(!(drives_ & (1 << i)))
{
continue;
}
char driveCh = static_cast<char>('A' + i);
char selectableStr[] = { driveCh, ':', '\0' };
bool selected = currentDrive == driveCh;
if(Selectable(selectableStr, selected) && !selected)
{
char newPwd[] = { driveCh, ':', '\\', '\0' };
@@ -342,7 +391,7 @@ inline void ImGui::FileBrowser::Display()
#endif
int secIdx = 0, newPwdLastSecIdx = -1;
for(auto &sec : pwd_)
for(const auto &sec : pwd_)
{
#ifdef _WIN32
if(secIdx == 1)
@@ -351,12 +400,18 @@ inline void ImGui::FileBrowser::Display()
continue;
}
#endif
PushID(secIdx);
if(secIdx > 0)
{
SameLine();
}
if(SmallButton(u8StrToStr(sec.u8string()).c_str()))
{
newPwdLastSecIdx = secIdx;
}
PopID();
++secIdx;
}
@@ -364,23 +419,52 @@ inline void ImGui::FileBrowser::Display()
{
int i = 0;
std::filesystem::path newPwd;
for(auto &sec : pwd_)
for(const auto &sec : pwd_)
{
if(i++ > newPwdLastSecIdx)
{
break;
}
newPwd /= sec;
}
#ifdef _WIN32
if(newPwdLastSecIdx == 0)
{
newPwd /= "\\";
}
#endif
SetPwd(newPwd);
}
SameLine();
if(SmallButton("*"))
SetPwd(pwd_);
{
UpdateFileRecords();
std::set<std::filesystem::path> newSelectedFilenames;
for(auto &name : selectedFilenames_)
{
auto it = std::find_if(
fileRecords_.begin(), fileRecords_.end(),
[&](const FileRecord &record)
{
return name == record.name;
});
if(it != fileRecords_.end())
{
newSelectedFilenames.insert(name);
}
}
if(inputNameBuf_ && (*inputNameBuf_)[0])
{
newSelectedFilenames.insert(inputNameBuf_->data());
}
}
if(newDirNameBuf_)
{
@@ -402,7 +486,9 @@ inline void ImGui::FileBrowser::Display()
{
ScopeGuard closeNewDirPopup([] { CloseCurrentPopup(); });
if(create_directory(pwd_ / newDirNameBuf_->data()))
SetPwd(pwd_);
{
UpdateFileRecords();
}
else
{
statusStr_ = "failed to create " +
@@ -428,10 +514,14 @@ inline void ImGui::FileBrowser::Display()
for(auto &rsc : fileRecords_)
{
if(!rsc.isDir && !IsExtensionMatched(rsc.extension))
{
continue;
}
if(!rsc.name.empty() && rsc.name.c_str()[0] == '$')
{
continue;
}
bool selected = selectedFilenames_.find(rsc.name)
!= selectedFilenames_.end();
@@ -447,9 +537,13 @@ inline void ImGui::FileBrowser::Display()
if(selected)
{
if(!multiSelect)
{
selectedFilenames_.clear();
}
else
{
selectedFilenames_.erase(rsc.name);
}
(*inputNameBuf_)[0] = '\0';
}
@@ -458,9 +552,15 @@ inline void ImGui::FileBrowser::Display()
if((rsc.isDir && (flags_ & ImGuiFileBrowserFlags_SelectDirectory)) ||
(!rsc.isDir && !(flags_ & ImGuiFileBrowserFlags_SelectDirectory)))
{
if(!multiSelect)
selectedFilenames_.clear();
if(multiSelect)
{
selectedFilenames_.insert(rsc.name);
}
else
{
selectedFilenames_ = { rsc.name };
}
if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory))
{
#ifdef _MSC_VER
@@ -478,9 +578,11 @@ inline void ImGui::FileBrowser::Display()
else
{
if(!multiSelect)
{
selectedFilenames_.clear();
}
}
}
if(IsItemClicked(0) && IsMouseDoubleClicked(0))
{
@@ -501,7 +603,9 @@ inline void ImGui::FileBrowser::Display()
}
if(setNewPwd)
{
SetPwd(newPwd);
}
if(!(flags_ & ImGuiFileBrowserFlags_SelectDirectory) &&
(flags_ & ImGuiFileBrowserFlags_EnterNewFilename))
@@ -537,12 +641,15 @@ inline void ImGui::FileBrowser::Display()
SameLine();
int escIdx = GetIO().KeyMap[ImGuiKey_Escape];
if(Button("cancel") || closeFlag_ ||
bool shouldExit =
Button("cancel") || closeFlag_ ||
((flags_ & ImGuiFileBrowserFlags_CloseOnEsc) &&
IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows) &&
escIdx >= 0 && IsKeyPressed(escIdx)))
IsKeyPressed(ImGuiKey_Escape));
if(shouldExit)
{
CloseCurrentPopup();
}
if(!statusStr_.empty() && !(flags_ & ImGuiFileBrowserFlags_NoStatusBar))
{
@@ -561,9 +668,11 @@ inline void ImGui::FileBrowser::Display()
for(size_t i = 0; i < typeFilters_.size(); ++i)
{
bool selected = static_cast<int>(i) == typeFilterIndex_;
bool selected = i == typeFilterIndex_;
if(Selectable(typeFilters_[i].c_str(), selected) && !selected)
typeFilterIndex_ = static_cast<int>(i);
{
typeFilterIndex_ = static_cast<unsigned int>(i);
}
}
}
PopItemWidth();
@@ -605,7 +714,9 @@ inline std::filesystem::path ImGui::FileBrowser::GetSelected() const
// when ok_ is true, selectedFilenames_ may be empty if SelectDirectory
// is enabled. return pwd in that case.
if(selectedFilenames_.empty())
{
return pwd_;
}
return pwd_ / *selectedFilenames_.begin();
}
@@ -613,11 +724,17 @@ inline std::vector<std::filesystem::path>
ImGui::FileBrowser::GetMultiSelected() const
{
if(selectedFilenames_.empty())
{
return { pwd_ };
}
std::vector<std::filesystem::path> ret;
ret.reserve(selectedFilenames_.size());
for(auto &s : selectedFilenames_)
{
ret.push_back(pwd_ / s);
}
return ret;
}
@@ -641,10 +758,12 @@ inline void ImGui::FileBrowser::SetTypeFilters(
for(auto &rawFilter : _typeFilters)
{
std::string lowerFilter = ToLower(rawFilter);
if(std::find(typeFilters.begin(), typeFilters.end(), lowerFilter) ==
typeFilters.end())
auto it = std::find(typeFilters.begin(), typeFilters.end(), lowerFilter);
if(it == typeFilters.end())
{
typeFilters.push_back(std::move(lowerFilter));
}
}
#else
@@ -667,13 +786,17 @@ inline void ImGui::FileBrowser::SetTypeFilters(
}
if(i > 0)
{
allFiltersName += ",";
}
allFiltersName += typeFilters[i];
}
if(hasAllFilter_)
{
typeFilters_.push_back(std::move(allFiltersName));
}
}
std::copy(
typeFilters.begin(), typeFilters.end(),
@@ -684,35 +807,60 @@ inline void ImGui::FileBrowser::SetTypeFilters(
inline void ImGui::FileBrowser::SetCurrentTypeFilterIndex(int index)
{
typeFilterIndex_ = index;
typeFilterIndex_ = static_cast<unsigned int>(index);
}
inline void ImGui::FileBrowser::SetInputName(std::string_view input)
{
if(flags_ & ImGuiFileBrowserFlags_EnterNewFilename)
{
if(input.size() >= static_cast<size_t>(INPUT_NAME_BUF_SIZE))
{
// If input doesn't fit trim off characters
input = input.substr(0, INPUT_NAME_BUF_SIZE - 1);
}
std::copy(input.begin(), input.end(), inputNameBuf_->begin());
inputNameBuf_->at(input.size()) = '\0';
selectedFilenames_ = { inputNameBuf_->data() };
}
}
inline std::string ImGui::FileBrowser::ToLower(const std::string &s)
{
std::string ret = s;
for(char &c : ret)
{
c = static_cast<char>(std::tolower(c));
}
return ret;
}
inline void ImGui::FileBrowser::SetPwdUncatched(const std::filesystem::path &pwd)
inline void ImGui::FileBrowser::UpdateFileRecords()
{
fileRecords_ = { FileRecord{ true, "..", "[D] ..", "" } };
for(auto &p : std::filesystem::directory_iterator(pwd))
for(auto &p : std::filesystem::directory_iterator(pwd_))
{
FileRecord rcd;
if(p.is_regular_file())
{
rcd.isDir = false;
}
else if(p.is_directory())
{
rcd.isDir = true;
}
else
{
continue;
}
rcd.name = p.path().filename();
if(rcd.name.empty())
{
continue;
}
rcd.extension = p.path().filename().extension();
@@ -726,8 +874,12 @@ inline void ImGui::FileBrowser::SetPwdUncatched(const std::filesystem::path &pwd
{
return (L.isDir ^ R.isDir) ? L.isDir : (L.name < R.name);
});
}
inline void ImGui::FileBrowser::SetPwdUncatched(const std::filesystem::path &pwd)
{
pwd_ = absolute(pwd);
UpdateFileRecords();
selectedFilenames_.clear();
(*inputNameBuf_)[0] = '\0';
}
@@ -743,11 +895,15 @@ inline bool ImGui::FileBrowser::IsExtensionMatched(
// no type filters
if(typeFilters_.empty())
{
return true;
}
// invalid type filter index
if(static_cast<size_t>(typeFilterIndex_) >= typeFilters_.size())
{
return true;
}
// all type filters
if(hasAllFilter_ && typeFilterIndex_ == 0)
@@ -755,14 +911,18 @@ inline bool ImGui::FileBrowser::IsExtensionMatched(
for(size_t i = 1; i < typeFilters_.size(); ++i)
{
if(extension == typeFilters_[i])
{
return true;
}
}
return false;
}
// universal filter
if(typeFilters_[typeFilterIndex_] == std::string_view(".*"))
{
return true;
}
// regular filter
return extension == typeFilters_[typeFilterIndex_];
@@ -807,12 +967,16 @@ inline std::uint32_t ImGui::FileBrowser::GetDrivesBitMask()
for(int i = 0; i < 26; ++i)
{
if(!(mask & (1 << i)))
{
continue;
}
char rootName[4] = { static_cast<char>('A' + i), ':', '\\', '\0' };
UINT type = GetDriveTypeA(rootName);
if(type == DRIVE_REMOVABLE || type == DRIVE_FIXED)
if(type == DRIVE_REMOVABLE || type == DRIVE_FIXED || type == DRIVE_REMOTE)
{
ret |= (1 << i);
}
}
return ret;
}
+643 -291
View File
File diff suppressed because it is too large Load Diff
+140 -138
View File
@@ -1,16 +1,20 @@
// Mini memory editor for Dear ImGui (to embed in your game/tools)
// Animated GIF: https://twitter.com/ocornut/status/894242704317530112
// Get latest version at http://www.github.com/ocornut/imgui_club
//
// Right-click anywhere to access the Options menu!
// You can adjust the keyboard repeat delay/rate in ImGuiIO.
// The code assume a mono-space font for simplicity! If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before caling this.
// The code assume a mono-space font for simplicity!
// If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before calling this.
//
// Usage:
// static MemoryEditor mem_edit_1; // store your state somewhere
// mem_edit_1.DrawWindow("Memory Editor", mem_block, mem_block_size, 0x0000); // create a window and draw memory editor (if you already have a window, use DrawContents())
// // Create a window and draw memory editor inside it:
// static MemoryEditor mem_edit_1;
// static char data[0x10000];
// size_t data_size = 0x10000;
// mem_edit_1.DrawWindow("Memory Editor", data, data_size);
//
// Usage:
// // If you already have a window, use DrawContents() instead:
// static MemoryEditor mem_edit_2;
// ImGui::Begin("MyWindow")
// mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this);
@@ -18,33 +22,32 @@
//
// Changelog:
// - v0.10: initial version
// - v0.11: always refresh active text input with the latest byte from source memory if it's not being edited.
// - v0.12: added OptMidRowsCount to allow extra spacing every XX rows.
// - v0.13: added optional ReadFn/WriteFn handlers to access memory via a function. various warning fixes for 64-bits.
// - v0.14: added GotoAddr member, added GotoAddrAndHighlight() and highlighting. fixed minor scrollbar glitch when resizing.
// - v0.15: added maximum window width. minor optimization.
// - v0.16: added OptGreyOutZeroes option. various sizing fixes when resizing using the "Rows" drag.
// - v0.17: added HighlightFn handler for optional non-contiguous highlighting.
// - v0.18: fixes for displaying 64-bits addresses, fixed mouse click gaps introduced in recent changes, cursor tracking scrolling fixes.
// - v0.19: fixed auto-focus of next byte leaving WantCaptureKeyboard=false for one frame. we now capture the keyboard during that transition.
// - v0.20: added options menu. added OptShowAscii checkbox. added optional HexII display. split Draw() in DrawWindow()/DrawContents(). fixing glyph width. refactoring/cleaning code.
// - v0.21: fixes for using DrawContents() in our own window. fixed HexII to actually be useful and not on the wrong side.
// - v0.22: clicking Ascii view select the byte in the Hex view. Ascii view highlight selection.
// - v0.23: fixed right-arrow triggering a byte write.
// - v0.24: changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61).
// - v0.25: fixed wording: all occurrences of "Rows" renamed to "Columns".
// - v0.26: fixed clicking on hex region
// - v0.30: added data preview for common data types
// - v0.31: added OptUpperCaseHex option to select lower/upper casing display [@samhocevar]
// - v0.32: changed signatures to use void* instead of unsigned char*
// - v0.33: added OptShowOptions option to hide all the interactive option setting.
// - v0.34: binary preview now applies endianess setting [@nicolasnoble]
// - v0.23 (2017/08/17): added to github. fixed right-arrow triggering a byte write.
// - v0.24 (2018/06/02): changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61).
// - v0.25 (2018/07/11): fixed wording: all occurrences of "Rows" renamed to "Columns".
// - v0.26 (2018/08/02): fixed clicking on hex region
// - v0.30 (2018/08/02): added data preview for common data types
// - v0.31 (2018/10/10): added OptUpperCaseHex option to select lower/upper casing display [@samhocevar]
// - v0.32 (2018/10/10): changed signatures to use void* instead of unsigned char*
// - v0.33 (2018/10/10): added OptShowOptions option to hide all the interactive option setting.
// - v0.34 (2019/05/07): binary preview now applies endianness setting [@nicolasnoble]
// - v0.35 (2020/01/29): using ImGuiDataType available since Dear ImGui 1.69.
// - v0.36 (2020/05/05): minor tweaks, minor refactor.
// - v0.40 (2020/10/04): fix misuse of ImGuiListClipper API, broke with Dear ImGui 1.79. made cursor position appears on left-side of edit box. option popup appears on mouse release. fix MSVC warnings where _CRT_SECURE_NO_WARNINGS wasn't working in recent versions.
// - v0.41 (2020/10/05): fix when using with keyboard/gamepad navigation enabled.
// - v0.42 (2020/10/14): fix for . character in ASCII view always being greyed out.
// - v0.43 (2021/03/12): added OptFooterExtraHeight to allow for custom drawing at the bottom of the editor [@leiradel]
// - v0.44 (2021/03/12): use ImGuiInputTextFlags_AlwaysOverwrite in 1.82 + fix hardcoded width.
// - v0.50 (2021/11/12): various fixes for recent dear imgui versions (fixed misuse of clipper, relying on SetKeyboardFocusHere() handling scrolling from 1.85). added default size.
//
// Todo/Bugs:
// - This is generally old/crappy code, it should work but isn't very good.. to be rewritten some day.
// - PageUp/PageDown are supported because we use _NoNav. This is a good test scenario for working out idioms of how to mix natural nav and our own...
// - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame.
// - Using InputText() is awkward and maybe overkill here, consider implementing something custom.
#pragma once
#include <stdio.h> // sprintf, scanf
#include <stdint.h> // uint8_t, etc.
@@ -56,25 +59,13 @@
#define ImSnprintf snprintf
#endif
#ifdef _MSC_VER
#pragma warning (push)
#pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe.
#endif
struct MemoryEditor
{
typedef unsigned char u8;
enum DataType
{
DataType_S8,
DataType_U8,
DataType_S16,
DataType_U16,
DataType_S32,
DataType_U32,
DataType_S64,
DataType_U64,
DataType_Float,
DataType_Double,
DataType_COUNT
};
enum DataFormat
{
DataFormat_Bin = 0,
@@ -95,10 +86,11 @@ struct MemoryEditor
bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff".
int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols.
int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr).
float OptFooterExtraHeight; // = 0 // space to reserve at the bottom of the widget to add custom widgets
ImU32 HighlightColor; // // background color of highlighted bytes.
u8 (*ReadFn)(const u8* data, size_t off); // = NULL // optional handler to read bytes.
void (*WriteFn)(u8* data, size_t off, u8 d); // = NULL // optional handler to write bytes.
bool (*HighlightFn)(const u8* data, size_t off);//NULL // optional handler to return Highlight property (to support non-contiguous highlighting).
ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes.
void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* data, size_t off);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
// [Internal State]
bool ContentsWidthChanged;
@@ -110,7 +102,7 @@ struct MemoryEditor
size_t GotoAddr;
size_t HighlightMin, HighlightMax;
int PreviewEndianess;
DataType PreviewDataType;
ImGuiDataType PreviewDataType;
MemoryEditor()
{
@@ -126,6 +118,7 @@ struct MemoryEditor
OptUpperCaseHex = true;
OptMidColsCount = 8;
OptAddrDigitsCount = 0;
OptFooterExtraHeight = 0.0f;
HighlightColor = IM_COL32(255, 255, 255, 50);
ReadFn = NULL;
WriteFn = NULL;
@@ -140,7 +133,7 @@ struct MemoryEditor
GotoAddr = (size_t)-1;
HighlightMin = HighlightMax = (size_t)-1;
PreviewEndianess = 0;
PreviewDataType = DataType_S32;
PreviewDataType = ImGuiDataType_S32;
}
void GotoAddrAndHighlight(size_t addr_min, size_t addr_max)
@@ -162,6 +155,8 @@ struct MemoryEditor
float PosAsciiStart;
float PosAsciiEnd;
float WindowWidth;
Sizes() { memset(this, 0, sizeof(*this)); }
};
void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr)
@@ -193,12 +188,13 @@ struct MemoryEditor
{
Sizes s;
CalcSizes(s, mem_size, base_display_addr);
ImGui::SetNextWindowSize(ImVec2(s.WindowWidth, s.WindowWidth * 0.60f), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX));
Open = true;
if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar))
{
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseClicked(1))
if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseReleased(ImGuiMouseButton_Right))
ImGui::OpenPopup("context");
DrawContents(mem_data, mem_size, base_display_addr);
if (ContentsWidthChanged)
@@ -211,9 +207,12 @@ struct MemoryEditor
}
// Memory Editor contents only
void DrawContents(void* mem_data_void_ptr, size_t mem_size, size_t base_display_addr = 0x0000)
void DrawContents(void* mem_data_void, size_t mem_size, size_t base_display_addr = 0x0000)
{
u8* mem_data = (u8*)mem_data_void_ptr;
if (Cols < 1)
Cols = 1;
ImU8* mem_data = (ImU8*)mem_data_void;
Sizes s;
CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle();
@@ -221,21 +220,21 @@ struct MemoryEditor
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
const float height_separator = style.ItemSpacing.y;
float footer_height = 0;
float footer_height = OptFooterExtraHeight;
if (OptShowOptions)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1;
if (OptShowDataPreview)
footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3;
ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove);
ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function.
const int line_total_count = (int)((mem_size + Cols - 1) / Cols);
ImGuiListClipper clipper(line_total_count, s.LineHeight);
const size_t visible_start_addr = clipper.DisplayStart * Cols;
const size_t visible_end_addr = clipper.DisplayEnd * Cols;
ImGuiListClipper clipper;
clipper.Begin(line_total_count, s.LineHeight);
bool data_next = false;
@@ -246,23 +245,14 @@ struct MemoryEditor
size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0;
size_t data_editing_addr_backup = DataEditingAddr;
size_t data_editing_addr_next = (size_t)-1;
if (DataEditingAddr != (size_t)-1)
{
// Move cursor but only apply on next frame so scrolling with be synchronized (because currently we can't change the scrolling while the window is being rendered)
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && DataEditingAddr >= (size_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; DataEditingTakeFocus = true; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && DataEditingAddr < mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; DataEditingTakeFocus = true; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && DataEditingAddr > 0) { data_editing_addr_next = DataEditingAddr - 1; DataEditingTakeFocus = true; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && DataEditingAddr < mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; DataEditingTakeFocus = true; }
}
if (data_editing_addr_next != (size_t)-1 && (data_editing_addr_next / Cols) != (data_editing_addr_backup / Cols))
{
// Track cursor movements
const int scroll_offset = ((int)(data_editing_addr_next / Cols) - (int)(data_editing_addr_backup / Cols));
const bool scroll_desired = (scroll_offset < 0 && data_editing_addr_next < visible_start_addr + Cols * 2) || (scroll_offset > 0 && data_editing_addr_next > visible_end_addr - Cols * 2);
if (scroll_desired)
ImGui::SetScrollY(ImGui::GetScrollY() + scroll_offset * s.LineHeight);
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && (ptrdiff_t)DataEditingAddr >= (ptrdiff_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && (ptrdiff_t)DataEditingAddr > (ptrdiff_t)0) { data_editing_addr_next = DataEditingAddr - 1; }
else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && (ptrdiff_t)DataEditingAddr < (ptrdiff_t)mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; }
}
// Draw vertical separator
@@ -275,19 +265,14 @@ struct MemoryEditor
const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: ";
const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x";
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x";
const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x ";
while (clipper.Step())
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines
{
size_t addr = (size_t)(line_i * Cols);
// format fix
char addr_str_buf[512] {};
snprintf(addr_str_buf, sizeof(addr_str_buf), format_address, s.AddrDigitsCount, base_display_addr + addr);
ImGui::TextUnformatted(addr_str_buf);
ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr);
// Draw Hexadecimal
for (int n = 0; n < Cols && addr < mem_size; n++, addr++)
@@ -322,12 +307,10 @@ struct MemoryEditor
ImGui::PushID((void*)addr);
if (DataEditingTakeFocus)
{
ImGui::SetKeyboardFocusHere();
ImGui::CaptureKeyboardFromApp(true);
ImGui::SetKeyboardFocusHere(0);
sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr);
sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
}
ImGui::PushItemWidth(s.GlyphWidth * 2);
struct UserData
{
// FIXME: We should have a way to retrieve the text edit cursor position more easily in the API, this is rather tedious. This is such a ugly mess we may be better off not using InputText() at all here.
@@ -338,11 +321,13 @@ struct MemoryEditor
user_data->CursorPos = data->CursorPos;
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen)
{
// When not editing a byte, always rewrite its content (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there)
// When not editing a byte, always refresh its InputText content pulled from underlying memory data
// (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there)
data->DeleteChars(0, data->BufTextLen);
data->InsertChars(0, user_data->CurrentBufOverwrite);
data->SelectionStart = 0;
data->SelectionEnd = data->CursorPos = 2;
data->SelectionEnd = 2;
data->CursorPos = 0;
}
return 0;
}
@@ -352,13 +337,18 @@ struct MemoryEditor
UserData user_data;
user_data.CursorPos = -1;
sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]);
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_AlwaysInsertMode | ImGuiInputTextFlags_CallbackAlways;
if (ImGui::InputText("##data", DataInputBuf, 32, flags, UserData::Callback, &user_data))
ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_CallbackAlways;
#if IMGUI_VERSION_NUM >= 18104
flags |= ImGuiInputTextFlags_AlwaysOverwrite;
#else
flags |= ImGuiInputTextFlags_AlwaysInsertMode;
#endif
ImGui::SetNextItemWidth(s.GlyphWidth * 2);
if (ImGui::InputText("##data", DataInputBuf, IM_ARRAYSIZE(DataInputBuf), flags, UserData::Callback, &user_data))
data_write = data_next = true;
else if (!DataEditingTakeFocus && !ImGui::IsItemActive())
DataEditingAddr = data_editing_addr_next = (size_t)-1;
DataEditingTakeFocus = false;
ImGui::PopItemWidth();
if (user_data.CursorPos >= 2)
data_write = data_next = true;
if (data_editing_addr_next != (size_t)-1)
@@ -367,16 +357,16 @@ struct MemoryEditor
if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1)
{
if (WriteFn)
WriteFn(mem_data, addr, (u8)data_input_value);
WriteFn(mem_data, addr, (ImU8)data_input_value);
else
mem_data[addr] = (u8)data_input_value;
mem_data[addr] = (ImU8)data_input_value;
}
ImGui::PopID();
}
else
{
// NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on.
u8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
if (OptShowHexII)
{
@@ -426,16 +416,18 @@ struct MemoryEditor
}
unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr];
char display_c = (c < 32 || c >= 128) ? '.' : c;
draw_list->AddText(pos, (display_c == '.') ? color_disabled : color_text, &display_c, &display_c + 1);
draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1);
pos.x += s.GlyphWidth;
}
}
}
clipper.End();
ImGui::PopStyleVar(2);
ImGui::EndChild();
if (data_next && DataEditingAddr < mem_size)
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
ImGui::SetCursorPosX(s.WindowWidth);
if (data_next && DataEditingAddr + 1 < mem_size)
{
DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1;
DataEditingTakeFocus = true;
@@ -443,23 +435,37 @@ struct MemoryEditor
else if (data_editing_addr_next != (size_t)-1)
{
DataEditingAddr = DataPreviewAddr = data_editing_addr_next;
DataEditingTakeFocus = true;
}
bool next_show_data_preview = OptShowDataPreview;
const bool lock_show_data_preview = OptShowDataPreview;
if (OptShowOptions)
{
ImGui::Separator();
DrawOptionsLine(s, mem_data, mem_size, base_display_addr);
}
if (lock_show_data_preview)
{
ImGui::Separator();
DrawPreviewLine(s, mem_data, mem_size, base_display_addr);
}
}
void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr)
{
IM_UNUSED(mem_data);
ImGuiStyle& style = ImGui::GetStyle();
const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
// Options menu
if (ImGui::Button("Options"))
ImGui::OpenPopup("context");
if (ImGui::BeginPopup("context"))
{
ImGui::PushItemWidth(56);
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; }
ImGui::PopItemWidth();
ImGui::Checkbox("Show Data Preview", &next_show_data_preview);
ImGui::SetNextItemWidth(s.GlyphWidth * 7 + style.FramePadding.x * 2.0f);
if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; }
ImGui::Checkbox("Show Data Preview", &OptShowDataPreview);
ImGui::Checkbox("Show HexII", &OptShowHexII);
if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; }
ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes);
@@ -469,13 +475,10 @@ struct MemoryEditor
}
ImGui::SameLine();
char format_range_buf[512] {};
snprintf(format_range_buf, sizeof(format_range_buf), format_range,
s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
ImGui::TextUnformatted(format_range_buf);
ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1);
ImGui::SameLine();
ImGui::PushItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f);
if (ImGui::InputText("##addr", AddrInputBuf, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue))
ImGui::SetNextItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f);
if (ImGui::InputText("##addr", AddrInputBuf, IM_ARRAYSIZE(AddrInputBuf), ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue))
{
size_t goto_addr;
if (sscanf(AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1)
@@ -484,7 +487,6 @@ struct MemoryEditor
HighlightMin = HighlightMax = (size_t)-1;
}
}
ImGui::PopItemWidth();
if (GotoAddr != (size_t)-1)
{
@@ -500,58 +502,53 @@ struct MemoryEditor
}
}
if (OptShowDataPreview)
void DrawPreviewLine(const Sizes& s, void* mem_data_void, size_t mem_size, size_t base_display_addr)
{
ImGui::Separator();
IM_UNUSED(base_display_addr);
ImU8* mem_data = (ImU8*)mem_data_void;
ImGuiStyle& style = ImGui::GetStyle();
ImGui::AlignTextToFramePadding();
ImGui::Text("Preview as:");
ImGui::SameLine();
ImGui::PushItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::SetNextItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest))
{
for (int n = 0; n < DataType_COUNT; n++)
if (ImGui::Selectable(DataTypeGetDesc((DataType)n), PreviewDataType == n))
PreviewDataType = (DataType)n;
for (int n = 0; n < ImGuiDataType_COUNT; n++)
if (ImGui::Selectable(DataTypeGetDesc((ImGuiDataType)n), PreviewDataType == n))
PreviewDataType = (ImGuiDataType)n;
ImGui::EndCombo();
}
ImGui::PopItemWidth();
ImGui::SameLine();
ImGui::PushItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::SetNextItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x);
ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0");
ImGui::PopItemWidth();
char buf[128];
char buf[128] = "";
float x = s.GlyphWidth * 6.0f;
bool has_value = DataPreviewAddr != (size_t)-1;
if (has_value)
DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf));
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf));
ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
if (has_value)
DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf));
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf));
ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
if (has_value)
DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf));
DrawPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf));
buf[IM_ARRAYSIZE(buf) - 1] = 0;
ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A");
}
OptShowDataPreview = next_show_data_preview;
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child)
ImGui::SetCursorPosX(s.WindowWidth);
}
// Utilities for Data Preview
const char* DataTypeGetDesc(DataType data_type) const
const char* DataTypeGetDesc(ImGuiDataType data_type) const
{
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" };
IM_ASSERT(data_type >= 0 && data_type < DataType_COUNT);
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
return descs[data_type];
}
size_t DataTypeGetSize(DataType data_type) const
size_t DataTypeGetSize(ImGuiDataType data_type) const
{
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, 4, 8 };
IM_ASSERT(data_type >= 0 && data_type < DataType_COUNT);
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
return sizes[data_type];
}
@@ -627,7 +624,8 @@ struct MemoryEditor
return out_buf;
}
void DisplayPreviewData(size_t addr, const u8* mem_data, size_t mem_size, DataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
// [Internal]
void DrawPreviewData(size_t addr, const ImU8* mem_data, size_t mem_size, ImGuiDataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const
{
uint8_t buf[8];
size_t elem_size = DataTypeGetSize(data_type);
@@ -649,7 +647,7 @@ struct MemoryEditor
out_buf[0] = 0;
switch (data_type)
{
case DataType_S8:
case ImGuiDataType_S8:
{
int8_t int8 = 0;
EndianessCopy(&int8, buf, size);
@@ -657,7 +655,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; }
break;
}
case DataType_U8:
case ImGuiDataType_U8:
{
uint8_t uint8 = 0;
EndianessCopy(&uint8, buf, size);
@@ -665,7 +663,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; }
break;
}
case DataType_S16:
case ImGuiDataType_S16:
{
int16_t int16 = 0;
EndianessCopy(&int16, buf, size);
@@ -673,7 +671,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; }
break;
}
case DataType_U16:
case ImGuiDataType_U16:
{
uint16_t uint16 = 0;
EndianessCopy(&uint16, buf, size);
@@ -681,7 +679,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; }
break;
}
case DataType_S32:
case ImGuiDataType_S32:
{
int32_t int32 = 0;
EndianessCopy(&int32, buf, size);
@@ -689,7 +687,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; }
break;
}
case DataType_U32:
case ImGuiDataType_U32:
{
uint32_t uint32 = 0;
EndianessCopy(&uint32, buf, size);
@@ -697,7 +695,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; }
break;
}
case DataType_S64:
case ImGuiDataType_S64:
{
int64_t int64 = 0;
EndianessCopy(&int64, buf, size);
@@ -705,7 +703,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; }
break;
}
case DataType_U64:
case ImGuiDataType_U64:
{
uint64_t uint64 = 0;
EndianessCopy(&uint64, buf, size);
@@ -713,7 +711,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; }
break;
}
case DataType_Float:
case ImGuiDataType_Float:
{
float float32 = 0.0f;
EndianessCopy(&float32, buf, size);
@@ -721,7 +719,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; }
break;
}
case DataType_Double:
case ImGuiDataType_Double:
{
double float64 = 0.0;
EndianessCopy(&float64, buf, size);
@@ -729,7 +727,7 @@ struct MemoryEditor
if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; }
break;
}
case DataType_COUNT:
case ImGuiDataType_COUNT:
break;
} // Switch
IM_ASSERT(0); // Shouldn't reach
@@ -738,3 +736,7 @@ struct MemoryEditor
#undef _PRISizeT
#undef ImSnprintf
#ifdef _MSC_VER
#pragma warning (pop)
#endif
+155 -99
View File
@@ -1,4 +1,4 @@
// dear imgui, v1.88 WIP
// dear imgui, v1.89.5 WIP
// (tables and columns code)
/*
@@ -24,7 +24,7 @@ Index of this file:
*/
// Navigating this file:
// - In Visual Studio IDE: CTRL+comma ("Edit.NavigateTo") can follow symbols in comments, whereas CTRL+F12 ("Edit.GoToImplementation") cannot.
// - 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.
//-----------------------------------------------------------------------------
@@ -188,12 +188,12 @@ Index of this file:
#define _CRT_SECURE_NO_WARNINGS
#endif
#include "imgui.h"
#ifndef IMGUI_DISABLE
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "imgui.h"
#ifndef IMGUI_DISABLE
#include "imgui_internal.h"
// System includes
@@ -315,7 +315,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
return false;
// Sanity checks
IM_ASSERT(columns_count > 0 && columns_count <= IMGUI_TABLE_MAX_COLUMNS && "Only 1..64 columns allowed!");
IM_ASSERT(columns_count > 0 && columns_count < IMGUI_TABLE_MAX_COLUMNS);
if (flags & ImGuiTableFlags_ScrollX)
IM_ASSERT(inner_width >= 0.0f);
@@ -332,11 +332,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
// Acquire storage for the table
ImGuiTable* table = g.Tables.GetOrAddByKey(id);
const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1;
const ImGuiID instance_id = id + instance_no;
const ImGuiTableFlags table_last_flags = table->Flags;
if (instance_no > 0)
IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID");
// Acquire temporary buffers
const int table_idx = g.Tables.GetIndex(table);
@@ -352,17 +348,32 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
flags = TableFixFlags(flags, outer_window);
// Initialize
const int instance_no = (table->LastFrameActive != g.FrameCount) ? 0 : table->InstanceCurrent + 1;
table->ID = id;
table->Flags = flags;
table->InstanceCurrent = (ImS16)instance_no;
table->LastFrameActive = g.FrameCount;
table->OuterWindow = table->InnerWindow = outer_window;
table->ColumnsCount = columns_count;
table->IsLayoutLocked = false;
table->InnerWidth = inner_width;
temp_data->UserOuterSize = outer_size;
if (instance_no > 0 && table->InstanceDataExtra.Size < instance_no)
// Instance data (for instance 0, TableID == TableInstanceID)
ImGuiID instance_id;
table->InstanceCurrent = (ImS16)instance_no;
if (instance_no > 0)
{
IM_ASSERT(table->ColumnsCount == columns_count && "BeginTable(): Cannot change columns count mid-frame while preserving same ID");
if (table->InstanceDataExtra.Size < instance_no)
table->InstanceDataExtra.push_back(ImGuiTableInstanceData());
instance_id = GetIDWithSeed(instance_no, GetIDWithSeed("##Instances", NULL, id)); // Push "##Instance" followed by (int)instance_no in ID stack.
}
else
{
instance_id = id;
}
ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);
table_instance->TableInstanceID = instance_id;
// When not using a child window, WorkRect.Max will grow as we append contents.
if (use_child_window)
@@ -395,6 +406,14 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
table->OuterRect = table->InnerWindow->Rect();
table->InnerRect = table->InnerWindow->InnerRect;
IM_ASSERT(table->InnerWindow->WindowPadding.x == 0.0f && table->InnerWindow->WindowPadding.y == 0.0f && table->InnerWindow->WindowBorderSize == 0.0f);
// When using multiple instances, ensure they have the same amount of horizontal decorations (aka vertical scrollbar) so stretched columns can be aligned)
if (instance_no == 0)
{
table->HasScrollbarYPrev = table->HasScrollbarYCurr;
table->HasScrollbarYCurr = false;
}
table->HasScrollbarYCurr |= (table->InnerWindow->ScrollMax.y > 0.0f);
}
else
{
@@ -404,7 +423,9 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
}
// Push a standardized ID for both child-using and not-child-using tables
PushOverrideID(instance_id);
PushOverrideID(id);
if (instance_no > 0)
PushOverrideID(instance_id); // FIXME: Somehow this is not resolved by stack-tool, even tho GetIDWithSeed() submitted the symbol.
// Backup a copy of host window members we will modify
ImGuiWindow* inner_window = table->InnerWindow;
@@ -539,7 +560,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
if (table->RefScale != 0.0f && table->RefScale != new_ref_scale_unit)
{
const float scale_factor = new_ref_scale_unit / table->RefScale;
//IMGUI_DEBUG_LOG("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor);
//IMGUI_DEBUG_PRINT("[table] %08X RefScaleUnit %.3f -> %.3f, scaling width by %.3f\n", table->ID, table->RefScaleUnit, new_ref_scale_unit, scale_factor);
for (int n = 0; n < columns_count; n++)
table->Columns[n].WidthRequest = table->Columns[n].WidthRequest * scale_factor;
}
@@ -573,16 +594,22 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
void ImGui::TableBeginInitMemory(ImGuiTable* table, int columns_count)
{
// Allocate single buffer for our arrays
ImSpanAllocator<3> span_allocator;
const int columns_bit_array_size = (int)ImBitArrayGetStorageSizeInBytes(columns_count);
ImSpanAllocator<6> span_allocator;
span_allocator.Reserve(0, columns_count * sizeof(ImGuiTableColumn));
span_allocator.Reserve(1, columns_count * sizeof(ImGuiTableColumnIdx));
span_allocator.Reserve(2, columns_count * sizeof(ImGuiTableCellData), 4);
for (int n = 3; n < 6; n++)
span_allocator.Reserve(n, columns_bit_array_size);
table->RawData = IM_ALLOC(span_allocator.GetArenaSizeInBytes());
memset(table->RawData, 0, span_allocator.GetArenaSizeInBytes());
span_allocator.SetArenaBasePtr(table->RawData);
span_allocator.GetSpan(0, &table->Columns);
span_allocator.GetSpan(1, &table->DisplayOrderToIndex);
span_allocator.GetSpan(2, &table->RowCellData);
table->EnabledMaskByDisplayOrder = (ImU32*)span_allocator.GetSpanPtrBegin(3);
table->EnabledMaskByIndex = (ImU32*)span_allocator.GetSpanPtrBegin(4);
table->VisibleMaskByIndex = (ImU32*)span_allocator.GetSpanPtrBegin(5);
}
// Apply queued resizing/reordering/hiding requests
@@ -721,8 +748,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
const ImGuiTableFlags table_sizing_policy = (table->Flags & ImGuiTableFlags_SizingMask_);
table->IsDefaultDisplayOrder = true;
table->ColumnsEnabledCount = 0;
table->EnabledMaskByIndex = 0x00;
table->EnabledMaskByDisplayOrder = 0x00;
ImBitArrayClearAllBits(table->EnabledMaskByIndex, table->ColumnsCount);
ImBitArrayClearAllBits(table->EnabledMaskByDisplayOrder, table->ColumnsCount);
table->LeftMostEnabledColumn = -1;
table->MinColumnWidth = ImMax(1.0f, g.Style.FramePadding.x * 1.0f); // g.Style.ColumnsMinSpacing; // FIXME-TABLE
@@ -787,8 +814,8 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
else
table->LeftMostEnabledColumn = (ImGuiTableColumnIdx)column_n;
column->IndexWithinEnabledSet = table->ColumnsEnabledCount++;
table->EnabledMaskByIndex |= (ImU64)1 << column_n;
table->EnabledMaskByDisplayOrder |= (ImU64)1 << column->DisplayOrder;
ImBitArraySetBit(table->EnabledMaskByIndex, column_n);
ImBitArraySetBit(table->EnabledMaskByDisplayOrder, column->DisplayOrder);
prev_visible_column_idx = column_n;
IM_ASSERT(column->IndexWithinEnabledSet <= column->DisplayOrder);
@@ -836,7 +863,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
table->LeftMostStretchedColumn = table->RightMostStretchedColumn = -1;
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
{
if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n)))
if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n))
continue;
ImGuiTableColumn* column = &table->Columns[column_n];
@@ -852,7 +879,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
// Latch initial size for fixed columns and update it constantly for auto-resizing column (unless clipped!)
if (column->AutoFitQueue != 0x00)
column->WidthRequest = width_auto;
else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)))
else if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !column_is_resizable && column->IsRequestOutput)
column->WidthRequest = width_auto;
// FIXME-TABLE: Increase minimum size during init frame to avoid biasing auto-fitting widgets
@@ -888,17 +915,19 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
sum_width_requests += table->CellPaddingX * 2.0f;
}
table->ColumnsEnabledFixedCount = (ImGuiTableColumnIdx)count_fixed;
table->ColumnsStretchSumWeights = stretch_sum_weights;
// [Part 4] Apply final widths based on requested widths
const ImRect work_rect = table->WorkRect;
const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);
const float width_avail = ((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth();
const float width_removed = (table->HasScrollbarYPrev && !table->InnerWindow->ScrollbarY) ? g.Style.ScrollbarSize : 0.0f; // To synchronize decoration width of synched tables with mismatching scrollbar state (#5920)
const float width_avail = ImMax(1.0f, (((table->Flags & ImGuiTableFlags_ScrollX) && table->InnerWidth == 0.0f) ? table->InnerClipRect.GetWidth() : work_rect.GetWidth()) - width_removed);
const float width_avail_for_stretched_columns = width_avail - width_spacings - sum_width_requests;
float width_remaining_for_stretched_columns = width_avail_for_stretched_columns;
table->ColumnsGivenWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount;
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
{
if (!(table->EnabledMaskByIndex & ((ImU64)1 << column_n)))
if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n))
continue;
ImGuiTableColumn* column = &table->Columns[column_n];
@@ -925,7 +954,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
if (width_remaining_for_stretched_columns >= 1.0f && !(table->Flags & ImGuiTableFlags_PreciseWidths))
for (int order_n = table->ColumnsCount - 1; stretch_sum_weights > 0.0f && width_remaining_for_stretched_columns >= 1.0f && order_n >= 0; order_n--)
{
if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))
continue;
ImGuiTableColumn* column = &table->Columns[table->DisplayOrderToIndex[order_n]];
if (!(column->Flags & ImGuiTableColumnFlags_WidthStretch))
@@ -935,11 +964,19 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
width_remaining_for_stretched_columns -= 1.0f;
}
// Determine if table is hovered which will be used to flag columns as hovered.
// - In principle we'd like to use the equivalent of IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem),
// but because our item is partially submitted at this point we use ItemHoverable() and a workaround (temporarily
// clear ActiveId, which is equivalent to the change provided by _AllowWhenBLockedByActiveItem).
// - This allows columns to be marked as hovered when e.g. clicking a button inside the column, or using drag and drop.
ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent);
table->HoveredColumnBody = -1;
table->HoveredColumnBorder = -1;
const ImRect mouse_hit_rect(table->OuterRect.Min.x, table->OuterRect.Min.y, table->OuterRect.Max.x, ImMax(table->OuterRect.Max.y, table->OuterRect.Min.y + table_instance->LastOuterHeight));
const ImGuiID backup_active_id = g.ActiveId;
g.ActiveId = 0;
const bool is_hovering_table = ItemHoverable(mouse_hit_rect, 0);
g.ActiveId = backup_active_id;
// [Part 6] Setup final position, offset, skip/clip states and clipping rectangles, detect hovered column
// Process columns in their visible orders as we are comparing the visible order and adjusting host_clip_rect while looping.
@@ -948,14 +985,13 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
float offset_x = ((table->FreezeColumnsCount > 0) ? table->OuterRect.Min.x : work_rect.Min.x) + table->OuterPaddingX - table->CellSpacingX1;
ImRect host_clip_rect = table->InnerClipRect;
//host_clip_rect.Max.x += table->CellPaddingX + table->CellSpacingX2;
table->VisibleMaskByIndex = 0x00;
table->RequestOutputMaskByIndex = 0x00;
ImBitArrayClearAllBits(table->VisibleMaskByIndex, table->ColumnsCount);
for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
{
const int column_n = table->DisplayOrderToIndex[order_n];
ImGuiTableColumn* column = &table->Columns[column_n];
column->NavLayerCurrent = (ImS8)((table->FreezeRowsCount > 0 || column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main);
column->NavLayerCurrent = (ImS8)(table->FreezeRowsCount > 0 ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main); // Use Count NOT request so Header line changes layer when frozen
if (offset_x_frozen && table->FreezeColumnsCount == visible_n)
{
@@ -966,7 +1002,7 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
// Clear status flags
column->Flags &= ~ImGuiTableColumnFlags_StatusMask_;
if ((table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)) == 0)
if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))
{
// Hidden column: clear a few fields and we are done with it for the remainder of the function.
// We set a zero-width clip rect but set Min.y/Max.y properly to not interfere with the clipper.
@@ -1019,12 +1055,10 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
column->IsVisibleY = true; // (column->ClipRect.Max.y > column->ClipRect.Min.y);
const bool is_visible = column->IsVisibleX; //&& column->IsVisibleY;
if (is_visible)
table->VisibleMaskByIndex |= ((ImU64)1 << column_n);
ImBitArraySetBit(table->VisibleMaskByIndex, column_n);
// Mark column as requesting output from user. Note that fixed + non-resizable sets are auto-fitting at all times and therefore always request output.
column->IsRequestOutput = is_visible || column->AutoFitQueue != 0 || column->CannotSkipItemsQueue != 0;
if (column->IsRequestOutput)
table->RequestOutputMaskByIndex |= ((ImU64)1 << column_n);
// Mark column as SkipItems (ignoring all items/layout)
column->IsSkipItems = !column->IsEnabled || table->HostSkipItems;
@@ -1104,25 +1138,24 @@ void ImGui::TableUpdateLayout(ImGuiTable* table)
table->IsUsingHeaders = false;
// [Part 11] Context menu
if (table->IsContextPopupOpen && table->InstanceCurrent == table->InstanceInteracted)
{
const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID);
if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings))
if (TableBeginContextMenuPopup(table))
{
TableDrawContextMenu(table);
EndPopup();
}
else
{
table->IsContextPopupOpen = false;
}
}
// [Part 13] Sanitize and build sort specs before we have a change to use them for display.
// [Part 12] Sanitize and build sort specs before we have a change to use them for display.
// This path will only be exercised when sort specs are modified before header rows (e.g. init or visibility change)
if (table->IsSortSpecsDirty && (table->Flags & ImGuiTableFlags_Sortable))
TableSortSpecsBuild(table);
// [Part 13] Setup inner window decoration size (for scrolling / nav tracking to properly take account of frozen rows/columns)
if (table->FreezeColumnsRequest > 0)
table->InnerWindow->DecoInnerSizeX1 = table->Columns[table->DisplayOrderToIndex[table->FreezeColumnsRequest - 1]].MaxX - table->OuterRect.Min.x;
if (table->FreezeRowsRequest > 0)
table->InnerWindow->DecoInnerSizeY1 = table_instance->LastFrozenHeight;
table_instance->LastFrozenHeight = 0.0f;
// Initial state
ImGuiWindow* inner_window = table->InnerWindow;
if (table->Flags & ImGuiTableFlags_NoClip)
@@ -1152,7 +1185,7 @@ void ImGui::TableUpdateBorders(ImGuiTable* table)
for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
{
if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))
continue;
const int column_n = table->DisplayOrderToIndex[order_n];
@@ -1170,8 +1203,8 @@ void ImGui::TableUpdateBorders(ImGuiTable* table)
ImGuiID column_id = TableGetColumnResizeID(table, column_n, table->InstanceCurrent);
ImRect hit_rect(column->MaxX - hit_half_width, hit_y1, column->MaxX + hit_half_width, border_y2_hit);
ItemAdd(hit_rect, column_id, NULL, ImGuiItemFlags_NoNav);
//GetForegroundDrawList()->AddRect(hit_rect.Min, hit_rect.Max, IM_COL32(255, 0, 0, 100));
KeepAliveID(column_id);
bool hovered = false, held = false;
bool pressed = ButtonBehavior(hit_rect, column_id, &hovered, &held, ImGuiButtonFlags_FlattenChildren | ImGuiButtonFlags_AllowItemOverlap | ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_NoNavFocus);
@@ -1284,17 +1317,23 @@ void ImGui::EndTable()
splitter->Merge(inner_window->DrawList);
// Update ColumnsAutoFitWidth to get us ahead for host using our size to auto-resize without waiting for next BeginTable()
const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);
table->ColumnsAutoFitWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount;
float auto_fit_width_for_fixed = 0.0f;
float auto_fit_width_for_stretched = 0.0f;
float auto_fit_width_for_stretched_min = 0.0f;
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
if (table->EnabledMaskByIndex & ((ImU64)1 << column_n))
if (IM_BITARRAY_TESTBIT(table->EnabledMaskByIndex, column_n))
{
ImGuiTableColumn* column = &table->Columns[column_n];
if ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize))
table->ColumnsAutoFitWidth += column->WidthRequest;
float column_width_request = ((column->Flags & ImGuiTableColumnFlags_WidthFixed) && !(column->Flags & ImGuiTableColumnFlags_NoResize)) ? column->WidthRequest : TableGetColumnWidthAuto(table, column);
if (column->Flags & ImGuiTableColumnFlags_WidthFixed)
auto_fit_width_for_fixed += column_width_request;
else
table->ColumnsAutoFitWidth += TableGetColumnWidthAuto(table, column);
auto_fit_width_for_stretched += column_width_request;
if ((column->Flags & ImGuiTableColumnFlags_WidthStretch) && (column->Flags & ImGuiTableColumnFlags_NoResize) != 0)
auto_fit_width_for_stretched_min = ImMax(auto_fit_width_for_stretched_min, column_width_request / (column->StretchWeight / table->ColumnsStretchSumWeights));
}
const float width_spacings = (table->OuterPaddingX * 2.0f) + (table->CellSpacingX1 + table->CellSpacingX2) * (table->ColumnsEnabledCount - 1);
table->ColumnsAutoFitWidth = width_spacings + (table->CellPaddingX * 2.0f) * table->ColumnsEnabledCount + auto_fit_width_for_fixed + ImMax(auto_fit_width_for_stretched, auto_fit_width_for_stretched_min);
// Update scroll
if ((table->Flags & ImGuiTableFlags_ScrollX) == 0 && inner_window != outer_window)
@@ -1322,8 +1361,10 @@ void ImGui::EndTable()
}
// Pop from id stack
IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table->ID + table->InstanceCurrent, "Mismatching PushID/PopID!");
IM_ASSERT_USER_ERROR(inner_window->IDStack.back() == table_instance->TableInstanceID, "Mismatching PushID/PopID!");
IM_ASSERT_USER_ERROR(outer_window->DC.ItemWidthStack.Size >= temp_data->HostBackupItemWidthStackSize, "Too many PopItemWidth!");
if (table->InstanceCurrent > 0)
PopID();
PopID();
// Restore window data that we modified
@@ -1593,11 +1634,11 @@ ImRect ImGui::TableGetCellBgRect(const ImGuiTable* table, int column_n)
}
// Return the resizing ID for the right-side of the given column.
ImGuiID ImGui::TableGetColumnResizeID(const ImGuiTable* table, int column_n, int instance_no)
ImGuiID ImGui::TableGetColumnResizeID(ImGuiTable* table, int column_n, int instance_no)
{
IM_ASSERT(column_n >= 0 && column_n < table->ColumnsCount);
ImGuiID id = table->ID + 1 + (instance_no * table->ColumnsCount) + column_n;
return id;
ImGuiID instance_id = TableGetInstanceID(table, instance_no);
return instance_id + 1 + column_n; // FIXME: #6140: still not ideal
}
// Return -1 when table is not hovered. return columns_count if the unused space at the right of visible columns is hovered.
@@ -1628,7 +1669,7 @@ void ImGui::TableSetBgColor(ImGuiTableBgTarget target, ImU32 color, int column_n
return;
if (column_n == -1)
column_n = table->CurrentColumn;
if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0)
if (!IM_BITARRAY_TESTBIT(table->VisibleMaskByIndex, column_n))
return;
if (table->RowCellDataCurrent < 0 || table->RowCellData[table->RowCellDataCurrent].Column != column_n)
table->RowCellDataCurrent++;
@@ -1718,6 +1759,8 @@ void ImGui::TableBeginRow(ImGuiTable* table)
table->RowTextBaseline = 0.0f;
table->RowIndentOffsetX = window->DC.Indent.x - table->HostIndentX; // Lock indent
window->DC.PrevLineTextBaseOffset = 0.0f;
window->DC.CurrLineSize = ImVec2(0.0f, 0.0f);
window->DC.IsSameLine = window->DC.IsSetPos = false;
window->DC.CursorMaxPos.y = next_y1;
// Making the header BG color non-transparent will allow us to overlay it multiple times when handling smooth dragging.
@@ -1830,17 +1873,15 @@ void ImGui::TableEndRow(ImGuiTable* table)
// get the new cursor position.
if (unfreeze_rows_request)
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
{
ImGuiTableColumn* column = &table->Columns[column_n];
column->NavLayerCurrent = (ImS8)((column_n < table->FreezeColumnsCount) ? ImGuiNavLayer_Menu : ImGuiNavLayer_Main);
}
table->Columns[column_n].NavLayerCurrent = ImGuiNavLayer_Main;
if (unfreeze_rows_actual)
{
IM_ASSERT(table->IsUnfrozenRows == false);
const float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y);
table->IsUnfrozenRows = true;
TableGetInstanceData(table, table->InstanceCurrent)->LastFrozenHeight = y0 - table->OuterRect.Min.y;
// BgClipRect starts as table->InnerClipRect, reduce it now and make BgClipRectForDrawCmd == BgClipRect
float y0 = ImMax(table->RowPosY2 + 1, window->InnerClipRect.Min.y);
table->BgClipRect.Min.y = table->Bg2ClipRectForDrawCmd.Min.y = ImMin(y0, window->InnerClipRect.Max.y);
table->BgClipRect.Max.y = table->Bg2ClipRectForDrawCmd.Max.y = window->InnerClipRect.Max.y;
table->Bg2DrawChannelCurrent = table->Bg2DrawChannelUnfrozen;
@@ -1903,7 +1944,7 @@ bool ImGui::TableSetColumnIndex(int column_n)
// Return whether the column is visible. User may choose to skip submitting items based on this return value,
// however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0;
return table->Columns[column_n].IsRequestOutput;
}
// [Public] Append into the next column, wrap and create a new row when already on last column
@@ -1928,8 +1969,7 @@ bool ImGui::TableNextColumn()
// Return whether the column is visible. User may choose to skip submitting items based on this return value,
// however they shouldn't skip submitting for columns that may have the tallest contribution to row height.
int column_n = table->CurrentColumn;
return (table->RequestOutputMaskByIndex & ((ImU64)1 << column_n)) != 0;
return table->Columns[table->CurrentColumn].IsRequestOutput;
}
@@ -1959,10 +1999,6 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n)
window->WorkRect.Max.x = column->WorkMaxX;
window->DC.ItemWidth = column->ItemWidth;
// To allow ImGuiListClipper to function we propagate our row height
if (!column->IsEnabled)
window->DC.CursorPos.y = ImMax(window->DC.CursorPos.y, table->RowPosY2);
window->SkipItems = column->IsSkipItems;
if (column->IsSkipItems)
{
@@ -1999,6 +2035,9 @@ void ImGui::TableEndCell(ImGuiTable* table)
ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];
ImGuiWindow* window = table->InnerWindow;
if (window->DC.IsSetPos)
ErrorCheckUsingSetCursorPosToExtendParentBoundaries();
// Report maximum position so we can infer content size per column.
float* p_max_pos_x;
if (table->RowFlags & ImGuiTableRowFlags_Headers)
@@ -2006,6 +2045,7 @@ void ImGui::TableEndCell(ImGuiTable* table)
else
p_max_pos_x = table->IsUnfrozenRows ? &column->ContentMaxXUnfrozen : &column->ContentMaxXFrozen;
*p_max_pos_x = ImMax(*p_max_pos_x, window->DC.CursorMaxPos.x);
if (column->IsEnabled)
table->RowPosY2 = ImMax(table->RowPosY2, window->DC.CursorMaxPos.y + table->CellPaddingY);
column->ItemWidth = window->DC.ItemWidth;
@@ -2093,7 +2133,7 @@ void ImGui::TableSetColumnWidth(int column_n, float width)
if (column_0->WidthGiven == column_0_width || column_0->WidthRequest == column_0_width)
return;
//IMGUI_DEBUG_LOG("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthGiven, column_0_width);
//IMGUI_DEBUG_PRINT("TableSetColumnWidth(%d, %.1f->%.1f)\n", column_0_idx, column_0->WidthGiven, column_0_width);
ImGuiTableColumn* column_1 = (column_0->NextEnabledColumn != -1) ? &table->Columns[column_0->NextEnabledColumn] : NULL;
// In this surprisingly not simple because of how we support mixing Fixed and multiple Stretch columns.
@@ -2266,7 +2306,7 @@ void ImGui::TableSetupDrawChannels(ImGuiTable* table)
const int freeze_row_multiplier = (table->FreezeRowsCount > 0) ? 2 : 1;
const int channels_for_row = (table->Flags & ImGuiTableFlags_NoClip) ? 1 : table->ColumnsEnabledCount;
const int channels_for_bg = 1 + 1 * freeze_row_multiplier;
const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || table->VisibleMaskByIndex != table->EnabledMaskByIndex) ? +1 : 0;
const int channels_for_dummy = (table->ColumnsEnabledCount < table->ColumnsCount || (memcmp(table->VisibleMaskByIndex, table->EnabledMaskByIndex, ImBitArrayGetStorageSizeInBytes(table->ColumnsCount)) != 0)) ? +1 : 0;
const int channels_total = channels_for_bg + (channels_for_row * freeze_row_multiplier) + channels_for_dummy;
table->DrawSplitter->Split(table->InnerWindow->DrawList, channels_total);
table->DummyDrawChannel = (ImGuiTableDrawChannelIdx)((channels_for_dummy > 0) ? channels_total - 1 : -1);
@@ -2342,17 +2382,24 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
{
ImRect ClipRect;
int ChannelsCount;
ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> ChannelsMask;
MergeGroup() { ChannelsCount = 0; }
ImBitArrayPtr ChannelsMask;
};
int merge_group_mask = 0x00;
MergeGroup merge_groups[4];
MergeGroup merge_groups[4] = {};
// Use a reusable temp buffer for the merge masks as they are dynamically sized.
const int max_draw_channels = (4 + table->ColumnsCount * 2);
const int size_for_masks_bitarrays_one = (int)ImBitArrayGetStorageSizeInBytes(max_draw_channels);
g.TempBuffer.reserve(size_for_masks_bitarrays_one * 5);
memset(g.TempBuffer.Data, 0, size_for_masks_bitarrays_one * 5);
for (int n = 0; n < IM_ARRAYSIZE(merge_groups); n++)
merge_groups[n].ChannelsMask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * n));
ImBitArrayPtr remaining_mask = (ImBitArrayPtr)(void*)(g.TempBuffer.Data + (size_for_masks_bitarrays_one * 4));
// 1. Scan channels and take note of those which can be merged
for (int column_n = 0; column_n < table->ColumnsCount; column_n++)
{
if ((table->VisibleMaskByIndex & ((ImU64)1 << column_n)) == 0)
if (!IM_BITARRAY_TESTBIT(table->VisibleMaskByIndex, column_n))
continue;
ImGuiTableColumn* column = &table->Columns[column_n];
@@ -2363,7 +2410,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
// Don't attempt to merge if there are multiple draw calls within the column
ImDrawChannel* src_channel = &splitter->_Channels[channel_no];
if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0 && src_channel->_CmdBuffer.back().UserCallback != NULL) // Equivalent of PopUnusedDrawCmd()
if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0 && src_channel->_CmdBuffer.back().UserCallback == NULL) // Equivalent of PopUnusedDrawCmd()
src_channel->_CmdBuffer.pop_back();
if (src_channel->_CmdBuffer.Size != 1)
continue;
@@ -2384,11 +2431,11 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
}
const int merge_group_n = (has_freeze_h && column_n < table->FreezeColumnsCount ? 0 : 1) + (has_freeze_v && merge_group_sub_n == 0 ? 0 : 2);
IM_ASSERT(channel_no < IMGUI_TABLE_MAX_DRAW_CHANNELS);
IM_ASSERT(channel_no < max_draw_channels);
MergeGroup* merge_group = &merge_groups[merge_group_n];
if (merge_group->ChannelsCount == 0)
merge_group->ClipRect = ImRect(+FLT_MAX, +FLT_MAX, -FLT_MAX, -FLT_MAX);
merge_group->ChannelsMask.SetBit(channel_no);
ImBitArraySetBit(merge_group->ChannelsMask, channel_no);
merge_group->ChannelsCount++;
merge_group->ClipRect.Add(src_channel->_CmdBuffer[0].ClipRect);
merge_group_mask |= (1 << merge_group_n);
@@ -2424,9 +2471,8 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
const int LEADING_DRAW_CHANNELS = 2;
g.DrawChannelsTempMergeBuffer.resize(splitter->_Count - LEADING_DRAW_CHANNELS); // Use shared temporary storage so the allocation gets amortized
ImDrawChannel* dst_tmp = g.DrawChannelsTempMergeBuffer.Data;
ImBitArray<IMGUI_TABLE_MAX_DRAW_CHANNELS> remaining_mask; // We need 132-bit of storage
remaining_mask.SetBitRange(LEADING_DRAW_CHANNELS, splitter->_Count);
remaining_mask.ClearBit(table->Bg2DrawChannelUnfrozen);
ImBitArraySetBitRange(remaining_mask, LEADING_DRAW_CHANNELS, splitter->_Count);
ImBitArrayClearBit(remaining_mask, table->Bg2DrawChannelUnfrozen);
IM_ASSERT(has_freeze_v == false || table->Bg2DrawChannelUnfrozen != TABLE_DRAW_CHANNEL_BG2_FROZEN);
int remaining_count = splitter->_Count - (has_freeze_v ? LEADING_DRAW_CHANNELS + 1 : LEADING_DRAW_CHANNELS);
//ImRect host_rect = (table->InnerWindow == table->OuterWindow) ? table->InnerClipRect : table->HostClipRect;
@@ -2459,14 +2505,14 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
GetOverlayDrawList()->AddLine(merge_group->ClipRect.Max, merge_clip_rect.Max, IM_COL32(255, 100, 0, 200));
#endif
remaining_count -= merge_group->ChannelsCount;
for (int n = 0; n < IM_ARRAYSIZE(remaining_mask.Storage); n++)
remaining_mask.Storage[n] &= ~merge_group->ChannelsMask.Storage[n];
for (int n = 0; n < (size_for_masks_bitarrays_one >> 2); n++)
remaining_mask[n] &= ~merge_group->ChannelsMask[n];
for (int n = 0; n < splitter->_Count && merge_channels_count != 0; n++)
{
// Copy + overwrite new clip rect
if (!merge_group->ChannelsMask.TestBit(n))
if (!IM_BITARRAY_TESTBIT(merge_group->ChannelsMask, n))
continue;
merge_group->ChannelsMask.ClearBit(n);
IM_BITARRAY_CLEARBIT(merge_group->ChannelsMask, n);
merge_channels_count--;
ImDrawChannel* channel = &splitter->_Channels[n];
@@ -2484,7 +2530,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table)
// Append unmergeable channels that we didn't reorder at the end of the list
for (int n = 0; n < splitter->_Count && remaining_count != 0; n++)
{
if (!remaining_mask.TestBit(n))
if (!IM_BITARRAY_TESTBIT(remaining_mask, n))
continue;
ImDrawChannel* channel = &splitter->_Channels[n];
memcpy(dst_tmp++, channel, sizeof(ImDrawChannel));
@@ -2516,7 +2562,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table)
{
for (int order_n = 0; order_n < table->ColumnsCount; order_n++)
{
if (!(table->EnabledMaskByDisplayOrder & ((ImU64)1 << order_n)))
if (!IM_BITARRAY_TESTBIT(table->EnabledMaskByDisplayOrder, order_n))
continue;
const int column_n = table->DisplayOrderToIndex[order_n];
@@ -2844,10 +2890,9 @@ void ImGui::TableHeadersRow()
continue;
// Push an id to allow unnamed labels (generally accidental, but let's behave nicely with them)
// - in your own code you may omit the PushID/PopID all-together, provided you know they won't collide
// - table->InstanceCurrent is only >0 when we use multiple BeginTable/EndTable calls with same identifier.
// In your own code you may omit the PushID/PopID all-together, provided you know they won't collide.
const char* name = (TableGetColumnFlags(column_n) & ImGuiTableColumnFlags_NoHeaderLabel) ? "" : TableGetColumnName(column_n);
PushID(table->InstanceCurrent * table->ColumnsCount + column_n);
PushID(column_n);
TableHeader(name);
PopID();
}
@@ -2993,7 +3038,7 @@ void ImGui::TableHeader(const char* label)
RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size);
const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x);
if (text_clipped && hovered && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay)
if (text_clipped && hovered && g.ActiveId == 0 && IsItemHovered(ImGuiHoveredFlags_DelayNormal))
SetTooltip("%.*s", (int)(label_end - label), label);
// We don't use BeginPopupContextItem() because we want the popup to stay up even after the column is hidden
@@ -3028,6 +3073,17 @@ void ImGui::TableOpenContextMenu(int column_n)
}
}
bool ImGui::TableBeginContextMenuPopup(ImGuiTable* table)
{
if (!table->IsContextPopupOpen || table->InstanceCurrent != table->InstanceInteracted)
return false;
const ImGuiID context_menu_id = ImHashStr("##ContextMenu", 0, table->ID);
if (BeginPopupEx(context_menu_id, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings))
return true;
table->IsContextPopupOpen = false;
return false;
}
// Output context menu into current window (generally a popup)
// FIXME-TABLE: Ideally this should be writable by the user. Full programmatic access to that data?
void ImGui::TableDrawContextMenu(ImGuiTable* table)
@@ -3047,15 +3103,15 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table)
if (column != NULL)
{
const bool can_resize = !(column->Flags & ImGuiTableColumnFlags_NoResize) && column->IsEnabled;
if (MenuItem("Size column to fit###SizeOne", NULL, false, can_resize))
if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableSizeOne), NULL, false, can_resize)) // "###SizeOne"
TableSetColumnWidthAutoSingle(table, column_n);
}
const char* size_all_desc;
if (table->ColumnsEnabledFixedCount == table->ColumnsEnabledCount && (table->Flags & ImGuiTableFlags_SizingMask_) != ImGuiTableFlags_SizingFixedSame)
size_all_desc = "Size all columns to fit###SizeAll"; // All fixed
size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllFit); // "###SizeAll" All fixed
else
size_all_desc = "Size all columns to default###SizeAll"; // All stretch or mixed
size_all_desc = LocalizeGetMsg(ImGuiLocKey_TableSizeAllDefault); // "###SizeAll" All stretch or mixed
if (MenuItem(size_all_desc, NULL))
TableSetColumnWidthAutoAll(table);
want_separator = true;
@@ -3064,7 +3120,7 @@ void ImGui::TableDrawContextMenu(ImGuiTable* table)
// Ordering
if (table->Flags & ImGuiTableFlags_Reorderable)
{
if (MenuItem("Reset order", NULL, false, !table->IsDefaultDisplayOrder))
if (MenuItem(LocalizeGetMsg(ImGuiLocKey_TableResetOrder), NULL, false, !table->IsDefaultDisplayOrder))
table->IsResetDisplayOrderRequest = true;
want_separator = true;
}
@@ -3457,7 +3513,7 @@ void ImGui::TableSettingsAddSettingsHandler()
// Remove Table (currently only used by TestEngine)
void ImGui::TableRemove(ImGuiTable* table)
{
//IMGUI_DEBUG_LOG("TableRemove() id=0x%08X\n", table->ID);
//IMGUI_DEBUG_PRINT("TableRemove() id=0x%08X\n", table->ID);
ImGuiContext& g = *GImGui;
int table_idx = g.Tables.GetIndex(table);
//memset(table->RawData.Data, 0, table->RawData.size_in_bytes());
@@ -3469,7 +3525,7 @@ void ImGui::TableRemove(ImGuiTable* table)
// Free up/compact internal Table buffers for when it gets unused
void ImGui::TableGcCompactTransientBuffers(ImGuiTable* table)
{
//IMGUI_DEBUG_LOG("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID);
//IMGUI_DEBUG_PRINT("TableGcCompactTransientBuffers() id=0x%08X\n", table->ID);
ImGuiContext& g = *GImGui;
IM_ASSERT(table->MemoryCompacted == false);
table->SortSpecs.Specs = NULL;
@@ -3513,7 +3569,7 @@ void ImGui::TableGcCompactSettings()
// - DebugNodeTable() [Internal]
//-------------------------------------------------------------------------
#ifndef IMGUI_DISABLE_METRICS_WINDOW
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
static const char* DebugNodeTableGetSizingPolicyDesc(ImGuiTableFlags sizing_policy)
{
@@ -3607,7 +3663,7 @@ void ImGui::DebugNodeTableSettings(ImGuiTableSettings* settings)
TreePop();
}
#else // #ifndef IMGUI_DISABLE_METRICS_WINDOW
#else // #ifndef IMGUI_DISABLE_DEBUG_TOOLS
void ImGui::DebugNodeTable(ImGuiTable*) {}
void ImGui::DebugNodeTableSettings(ImGuiTableSettings*) {}
@@ -3947,6 +4003,7 @@ void ImGui::NextColumn()
{
// New row/line: column 0 honor IndentX.
window->DC.ColumnsOffset.x = ImMax(column_padding - window->WindowPadding.x, 0.0f);
window->DC.IsSameLine = false;
columns->LineMinY = columns->LineMaxY;
}
window->DC.CursorPos.x = IM_FLOOR(window->Pos.x + window->DC.Indent.x + window->DC.ColumnsOffset.x);
@@ -3998,8 +4055,7 @@ void ImGui::EndColumns()
const ImGuiID column_id = columns->ID + ImGuiID(n);
const float column_hit_hw = COLUMNS_HIT_RECT_HALF_WIDTH;
const ImRect column_hit_rect(ImVec2(x - column_hit_hw, y1), ImVec2(x + column_hit_hw, y2));
KeepAliveID(column_id);
if (IsClippedEx(column_hit_rect, column_id)) // FIXME: Can be removed or replaced with a lower-level test
if (!ItemAdd(column_hit_rect, column_id, NULL, ImGuiItemFlags_NoNav))
continue;
bool hovered = false, held = false;
+831 -497
View File
File diff suppressed because it is too large Load Diff
+7 -17
View File
@@ -2,6 +2,7 @@
// This is a slightly modified version of stb_textedit.h 1.14.
// Those changes would need to be pushed into nothings/stb:
// - Fix in stb_textedit_discard_redo (see https://github.com/nothings/stb/issues/321)
// - Fix in stb_textedit_find_charpos to handle last line (see https://github.com/ocornut/imgui/issues/6000)
// Grep for [DEAR IMGUI] to find the changes.
// stb_textedit.h - v1.14 - public domain - Sean Barrett
@@ -524,29 +525,14 @@ static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *s
int z = STB_TEXTEDIT_STRINGLEN(str);
int i=0, first;
if (n == z) {
// if it's at the end, then find the last line -- simpler than trying to
// explicitly handle this case in the regular code
if (single_line) {
if (n == z && single_line) {
// special case if it's at the end (may not be needed?)
STB_TEXTEDIT_LAYOUTROW(&r, str, 0);
find->y = 0;
find->first_char = 0;
find->length = z;
find->height = r.ymax - r.ymin;
find->x = r.x1;
} else {
find->y = 0;
find->x = 0;
find->height = 1;
while (i < z) {
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
prev_start = i;
i += r.num_chars;
}
find->first_char = i;
find->length = 0;
find->prev_first = prev_start;
}
return;
}
@@ -557,9 +543,13 @@ static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *s
STB_TEXTEDIT_LAYOUTROW(&r, str, i);
if (n < i + r.num_chars)
break;
if (i + r.num_chars == z && z > 0 && STB_TEXTEDIT_GETCHAR(str, z - 1) != STB_TEXTEDIT_NEWLINE) // [DEAR IMGUI] special handling for last line
break; // [DEAR IMGUI]
prev_start = i;
i += r.num_chars;
find->y += r.baseline_y_delta;
if (i == z) // [DEAR IMGUI]
break; // [DEAR IMGUI]
}
find->first_char = first = i;
+1 -1
View File
@@ -2008,7 +2008,7 @@ static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int gly
start = end;
}
}
if (fdselector == -1) stbtt__new_buf(NULL, 0);
if (fdselector == -1) return stbtt__new_buf(NULL, 0); // [DEAR IMGUI] fixed, see #6007 and nothings/stb#1422
return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector));
}
+7 -7
View File
@@ -11,13 +11,13 @@ namespace games::bc {
Game::attach();
setupapihook_init(libutils::load_library("libaio.dll"));
setupapihook_add({
{ 0, 0, 0, 0, },
"",
"USB\\VID_1CCF&PID_8050\\0000",
{},
"COM3",
});
SETUPAPI_SETTINGS settings {};
const char hwid[] = "USB\\VID_1CCF&PID_8050\\0000";
memcpy(settings.property_hardwareid, hwid, sizeof(hwid));
const char comport[] = "COM3";
memcpy(settings.interface_detail, comport, sizeof(comport));
setupapihook_add(settings);
auto iob = new acio2emu::IOBHandle(L"COM3");
iob->register_node(std::make_unique<TBSNode>());
+88 -17
View File
@@ -15,6 +15,7 @@
#include "acioemu/handle.h"
#include "avs/core.h"
#include "avs/game.h"
#include "cfg/configurator.h"
#include "games/io.h"
#include "hooks/avshook.h"
#include "hooks/cfgmgr32hook.h"
@@ -23,6 +24,7 @@
#include "hooks/setupapihook.h"
#include "hooks/sleephook.h"
#include "launcher/options.h"
#include "touch/touch.h"
#include "misc/wintouchemu.h"
#include "misc/eamuse.h"
#include "util/detour.h"
@@ -56,15 +58,19 @@ namespace games::iidx {
bool FLIP_CAMS = false;
bool DISABLE_CAMS = false;
bool TDJ_MODE = false;
bool FORCE_720P = false;
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
std::optional<std::string> ASIO_DRIVER = std::nullopt;
uint8_t DIGITAL_TT_SENS = 4;
std::optional<std::string> SUBSCREEN_OVERLAY_SIZE = std::nullopt;
// states
static HKEY real_asio_reg_handle = nullptr;
static HKEY real_asio_device_reg_handle = nullptr;
static uint16_t IIDXIO_TT_STATE[2]{};
static uint16_t IIDXIO_TT_DIRECTION[2]{};
static int8_t IIDXIO_TT_DIRECTION[2]{1, 1};
static bool IIDXIO_TT_PRESSED[2]{};
static bool IIDXIO_TT_ALT_PRESSED[2]{};
char IIDXIO_LED_TICKER[10] = {' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\x00'};
bool IIDXIO_LED_TICKER_READONLY = false;
std::mutex IIDX_LED_TICKER_LOCK;
@@ -445,8 +451,8 @@ namespace games::iidx {
// need to hook `avs2-core.dll` so AVS win32fs operations go through rom hook
devicehook_init(avs::core::DLL_INSTANCE);
// hook touch window
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook_title_ends("beatmania IIDX", "main", avs::game::DLL_INSTANCE);
// prevent crash on TDJ mode without correct DLL
@@ -538,8 +544,21 @@ namespace games::iidx {
if (DISABLE_CAMS) {
SetEnvironmentVariable("CONNECT_CAMERA", "0");
}
if (SOUND_OUTPUT_DEVICE.has_value()) {
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", SOUND_OUTPUT_DEVICE.value().c_str());
this->detect_sound_output_device();
// check bad model name
if (!cfg::CONFIGURATOR_STANDALONE && avs::game::is_model("TDJ")) {
log_fatal(
"iidx",
"BAD MODEL NAME ERROR\n\n\n"
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n"
"If you are trying to boot IIDX with Lightning Model mode, please do the following instead:\n"
" Revert your changes to XML file so it says <model __type=\"str\">LDJ</model>\n"
" In SpiceCfg, enable 'IIDX TDJ Mode' or provide -iidxtdj flag in command line\n"
" Apply any applicable settings / patches / hex edits\n"
"!!! model name set to TDJ, this is WRONG and will break your game !!!\n\n\n"
);
}
}
@@ -549,6 +568,53 @@ namespace games::iidx {
devicehook_dispose();
}
void IIDXGame::detect_sound_output_device() {
// if the user specified a value (other than auto), use it as the environment var
// probably "wasapi" or "asio", but it's not explicitly checked here for forward compat
if (SOUND_OUTPUT_DEVICE.has_value() && SOUND_OUTPUT_DEVICE.value() != "auto") {
log_info(
"iidx",
"using user-supplied \"{}\" for SOUND_OUTPUT_DEVICE",
SOUND_OUTPUT_DEVICE.value());
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", SOUND_OUTPUT_DEVICE.value().c_str());
return;
}
// automatic detection
bool use_asio = false;
log_misc("iidx", "auto-detect SOUND_OUTPUT_DEVICE...");
if (ASIO_DRIVER.has_value()) {
log_misc(
"iidx",
"-iidxasio is set to \"{}\", use asio for SOUND_OUTPUT_DEVICE",
ASIO_DRIVER.value());
use_asio = true;
} else {
HKEY subkey;
LSTATUS result;
result = RegOpenKey(HKEY_LOCAL_MACHINE, "SOFTWARE\\ASIO\\XONAR SOUND CARD(64)", &subkey);
if (result == ERROR_SUCCESS) {
RegCloseKey(subkey);
use_asio = true;
log_misc(
"iidx",
"found HKLM\\SOFTWARE\\ASIO\\XONAR SOUND CARD(64), using asio for SOUND_OUTPUT_DEVICE");
}
}
const char* device = "wasapi";
if (use_asio) {
device = "asio";
}
log_info("iidx", "SOUND_OUTPUT_DEVICE set to {}", device);
log_info(
"iidx",
"SOUND_OUTPUT_DEVICE only affects IIDX27+ and ignored by older games. "
"If {} is not what you wanted, you can override it with -iidxsounddevice option",
device);
SetEnvironmentVariable("SOUND_OUTPUT_DEVICE", device);
}
uint32_t get_pad() {
uint32_t pad = 0;
@@ -701,7 +767,8 @@ namespace games::iidx {
unsigned char get_tt(int player, bool slow) {
// check change value for high/low precision
uint16_t change = slow ? (uint16_t) 1 : (uint16_t) 4;
uint16_t change =
slow ? (uint16_t) (DIGITAL_TT_SENS / 4) : (uint16_t) DIGITAL_TT_SENS;
// check player number
if (player > 1)
@@ -713,8 +780,11 @@ namespace games::iidx {
player != 0 ? Buttons::P2_TTPlus : Buttons::P1_TTPlus));
bool ttm = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTMinus : Buttons::P1_TTMinus));
bool ttpm = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlusMinus : Buttons::P1_TTPlusMinus));
bool ttpm_alt = GameAPI::Buttons::getState(RI_MGR, buttons.at(
player != 0 ? Buttons::P2_TTPlusMinusAlt : Buttons::P1_TTPlusMinusAlt));
// TT+
if (ttp)
@@ -725,19 +795,16 @@ namespace games::iidx {
IIDXIO_TT_STATE[player] -= change;
// TT+/-
if (ttpm) {
if (IIDXIO_TT_DIRECTION[player] != 0u) {
if (!IIDXIO_TT_PRESSED[player])
IIDXIO_TT_DIRECTION[player] = 0;
IIDXIO_TT_STATE[player] += change;
} else {
if (!IIDXIO_TT_PRESSED[player])
IIDXIO_TT_DIRECTION[player] = 1;
IIDXIO_TT_STATE[player] -= change;
bool ttpm_rising_edge = !IIDXIO_TT_PRESSED[player] && ttpm;
bool ttpm_alt_rising_edge = !IIDXIO_TT_ALT_PRESSED[player] && ttpm_alt;
if (ttpm_rising_edge || ttpm_alt_rising_edge) {
IIDXIO_TT_DIRECTION[player] *= -1;
}
IIDXIO_TT_PRESSED[player] = true;
} else
IIDXIO_TT_PRESSED[player] = false;
if (ttpm || ttpm_alt) {
IIDXIO_TT_STATE[player] += (change * IIDXIO_TT_DIRECTION[player]);
}
IIDXIO_TT_PRESSED[player] = ttpm;
IIDXIO_TT_ALT_PRESSED[player] = ttpm_alt;
// raw input
auto &analogs = get_analogs();
@@ -793,4 +860,8 @@ namespace games::iidx {
const char* get_16seg() {
return IIDXIO_LED_TICKER;
}
bool is_tdj_fhd() {
return TDJ_MODE && avs::game::is_ext(2022101900, MAXINT);
}
}
+7
View File
@@ -11,8 +11,11 @@ namespace games::iidx {
extern bool FLIP_CAMS;
extern bool DISABLE_CAMS;
extern bool TDJ_MODE;
extern bool FORCE_720P;
extern std::optional<std::string> SOUND_OUTPUT_DEVICE;
extern std::optional<std::string> ASIO_DRIVER;
extern uint8_t DIGITAL_TT_SENS;
extern std::optional<std::string> SUBSCREEN_OVERLAY_SIZE;
// state
extern char IIDXIO_LED_TICKER[10];
@@ -27,6 +30,9 @@ namespace games::iidx {
virtual void attach() override;
virtual void pre_attach() override;
virtual void detach() override;
private:
void detect_sound_output_device();
};
// helper methods
@@ -38,4 +44,5 @@ namespace games::iidx {
unsigned char get_tt(int player, bool slow);
unsigned char get_slider(uint8_t slider);
const char* get_16seg();
bool is_tdj_fhd();
}
+2
View File
@@ -21,6 +21,7 @@ std::vector<Button> &games::iidx::get_buttons() {
"P1 TT+",
"P1 TT-",
"P1 TT+/-",
"P1 TT+/- Alternate",
"P1 Start",
"P2 1",
"P2 2",
@@ -32,6 +33,7 @@ std::vector<Button> &games::iidx::get_buttons() {
"P2 TT+",
"P2 TT-",
"P2 TT+/-",
"P2 TT+/- Alternate",
"P2 Start",
"EFFECT",
"VEFX",
+2
View File
@@ -21,6 +21,7 @@ namespace games::iidx {
P1_TTPlus,
P1_TTMinus,
P1_TTPlusMinus,
P1_TTPlusMinusAlt,
P1_Start,
P2_1,
P2_2,
@@ -32,6 +33,7 @@ namespace games::iidx {
P2_TTPlus,
P2_TTMinus,
P2_TTPlusMinus,
P2_TTPlusMinusAlt,
P2_Start,
Effect,
VEFX,
+12 -10
View File
@@ -366,8 +366,14 @@ namespace games {
std::vector<unsigned short> vkey_defaults;
// overlay button definitions
names.emplace_back("Screenshot");
vkey_defaults.push_back(VK_SNAPSHOT);
names.emplace_back("Toggle Sub Screen");
vkey_defaults.push_back(VK_PRIOR);
names.emplace_back("Insert Coin");
vkey_defaults.push_back(VK_F1);
names.emplace_back("Toggle IO Panel");
vkey_defaults.push_back(VK_F2);
names.emplace_back("Toggle Config");
vkey_defaults.push_back(VK_F4);
names.emplace_back("Toggle Virtual Keypad P1");
@@ -382,12 +388,14 @@ namespace games {
vkey_defaults.push_back(VK_F9);
names.emplace_back("Toggle Patch Manager");
vkey_defaults.push_back(VK_F10);
names.emplace_back("Toggle Sub Screen");
vkey_defaults.push_back(VK_PRIOR);
names.emplace_back("Toggle VR Control");
vkey_defaults.push_back(0xFF);
names.emplace_back("Toggle Screen Resize");
vkey_defaults.push_back(VK_F11);
names.emplace_back("Toggle Overlay");
vkey_defaults.push_back(VK_F12);
names.emplace_back("Toggle VR Control");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Activate");
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Cancel");
@@ -400,18 +408,12 @@ namespace games {
vkey_defaults.push_back(0xFF);
names.emplace_back("Navigator Right");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screenshot");
vkey_defaults.push_back(VK_SNAPSHOT);
names.emplace_back("Hotkey Enable 1");
vkey_defaults.push_back(0xFF);
names.emplace_back("Hotkey Enable 2");
vkey_defaults.push_back(0xFF);
names.emplace_back("Hotkey Toggle");
vkey_defaults.push_back(0xFF);
names.emplace_back("Screen Resize");
vkey_defaults.push_back(0xFF);
names.emplace_back("Toggle Screen Resize");
vkey_defaults.push_back(0xFF);
// return sorted buttons
buttons = GameAPI::Buttons::sortButtons(buttons, names, &vkey_defaults);
+6 -5
View File
@@ -7,7 +7,10 @@ namespace games {
namespace OverlayButtons {
enum {
Screenshot,
ToggleSubScreen,
InsertCoin,
ToggleIOPanel,
ToggleConfig,
ToggleVirtualKeypadP1,
ToggleVirtualKeypadP2,
@@ -15,21 +18,19 @@ namespace games {
ToggleLog,
ToggleControl,
TogglePatchManager,
ToggleSubScreen,
ToggleVRControl,
ToggleScreenResize,
ToggleOverlay,
ToggleVRControl,
ScreenResize,
NavigatorActivate,
NavigatorCancel,
NavigatorUp,
NavigatorDown,
NavigatorLeft,
NavigatorRight,
Screenshot,
HotkeyEnable1,
HotkeyEnable2,
HotkeyToggle,
ScreenResize,
ToggleScreenResize,
};
}
+47
View File
@@ -10,9 +10,14 @@
#include "util/detour.h"
#include "util/libutils.h"
#define JB_BUTTON_SIZE 160
#define JB_BUTTON_GAP 37
#define JB_BUTTON_HITBOX (JB_BUTTON_SIZE + JB_BUTTON_GAP)
namespace games::jb {
// touch stuff
bool TOUCH_LEGACY_BOX = false;
static bool TOUCH_ENABLE = false;
static bool TOUCH_ATTACHED = false;
static bool IS_PORTRAIT = true;
@@ -68,8 +73,10 @@ namespace games::jb {
memset(TOUCH_STATE, 0, sizeof(TOUCH_STATE));
// check touch points
// note that the IO code in device.cpp will correctly compensate for orientation, depending on the model.
TOUCH_POINTS.clear();
touch_get_points(TOUCH_POINTS);
if (TOUCH_LEGACY_BOX) {
auto offset = IS_PORTRAIT ? 580 : 0;
for (auto &tp : TOUCH_POINTS) {
@@ -83,6 +90,46 @@ namespace games::jb {
TOUCH_STATE[index] = true;
}
}
} else {
for (auto &tp : TOUCH_POINTS) {
int x_relative = tp.x;
int y_relative = tp.y;
// x_relative and y_relative are relative to the top-left pixel of the first button
if (IS_PORTRAIT) {
// which is at (8, 602) in portrait:
// X: [8...759] (752 pixels wide)
// Y: [602...1353] (752 pixels high)
x_relative -= 8;
y_relative -= 602;
} else {
// and at (8, 8) in landscape
x_relative -= 8;
y_relative -= 8;
}
if (x_relative < 0 || y_relative < 0) {
continue;
}
// x_hitbox and y_hitbox is relative to top-left pixel of each button
int x_index = x_relative / JB_BUTTON_HITBOX;
int x_hitbox = x_relative % JB_BUTTON_HITBOX;
int y_index = y_relative / JB_BUTTON_HITBOX;
int y_hitbox = y_relative % JB_BUTTON_HITBOX;
// check if the gap was touched
if (x_hitbox > JB_BUTTON_SIZE || y_hitbox > JB_BUTTON_SIZE) {
continue;
}
// set the corresponding state
int index = y_index * 4 + x_index;
if (0 <= index && index < 16) {
TOUCH_STATE[index] = true;
}
}
}
}
/*
+1
View File
@@ -5,6 +5,7 @@
namespace games::jb {
// touch stuff
extern bool TOUCH_LEGACY_BOX;
extern bool TOUCH_STATE[16];
void touch_update();
+2
View File
@@ -4,6 +4,8 @@
namespace games::rb {
extern uint16_t TOUCH_SCALING;
class RBGame : public games::Game {
public:
RBGame();
+21 -4
View File
@@ -11,15 +11,20 @@
static std::string WINDOW_TITLE = "REFLEC BEAT";
namespace games::rb {
uint16_t TOUCH_SCALING = 1000;
}
games::rb::ReflecBeatTouchDeviceHandle::ReflecBeatTouchDeviceHandle(bool log_fps) {
this->log_fps = log_fps;
}
void games::rb::ReflecBeatTouchDeviceHandle::grid_insert(unsigned char *data, int cursor_x, int cursor_y) {
// scale to grid position
// scale to grid position - there are 48 columns and 76 rows of IR sensors.
// for whatever reason, the last y row (#75) results in weird input few rows above; just drop it
int grid_x = CLAMP((cursor_x * 48) / window_width, 0, 47);
int grid_y = CLAMP((cursor_y * 76) / window_height, 0, 75);
int grid_y = CLAMP((cursor_y * 76) / window_height, 0, 74);
// get bit positions
int bit_x = 88 + grid_x;
@@ -122,6 +127,8 @@ int games::rb::ReflecBeatTouchDeviceHandle::read(LPVOID lpBuffer, DWORD nNumberO
data[0] = 0x55;
data[2] = 0x4C;
const auto SCALE_FACTOR = games::rb::TOUCH_SCALING / 1000.f;
// iterate all touch points
auto offset_x = (int) (window_width / 48.0 / 3.0);
auto offset_y = (int) (window_height / 76.0 / 3.0);
@@ -139,9 +146,19 @@ int games::rb::ReflecBeatTouchDeviceHandle::read(LPVOID lpBuffer, DWORD nNumberO
y = window_height - point.x;
}
// apply scaling
x = x - (window_width * (1.f - SCALE_FACTOR) / 2);
x = x / SCALE_FACTOR;
y = y - (window_height * (1.f - SCALE_FACTOR) / 2);
y = y / SCALE_FACTOR;
if (x < 0 || window_width <= x || y < 0 || window_height <= y) {
continue;
}
// point scaling
auto point_x = (int) ((x - window_width / 2.0) * 48.0 / 54.0 + window_width / 2.0);
auto point_y = (int) (y - window_height / 76);
const auto point_x = (int) ((x - window_width / 2.0) * 48.0 / 54.0 + window_width / 2.0);
const auto point_y = (int) (y - window_height / 76);
// insert 9 times with offset to double the precision
grid_insert(data, point_x, point_y);
+7 -5
View File
@@ -9,9 +9,11 @@
#include "games/io.h"
#include "launcher/options.h"
#include "io.h"
#include "games/sdvx/sdvx.h"
namespace games::sdvx {
constexpr bool BI2X_PASSTHROUGH = false;
bool BI2X_INITIALIZED = false;
/*
* class definitions
@@ -104,7 +106,6 @@ namespace games::sdvx {
if (!BI2X_PASSTHROUGH) {
custom_node = new AIO_IOB2_BI2X_UFC;
memset(&custom_node->data, 0, sizeof(custom_node->data));
return custom_node;
} else {
@@ -163,18 +164,18 @@ namespace games::sdvx {
// volume left
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Left])) {
VOL_L -= 64;
VOL_L -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_L_Right])) {
VOL_L += 64;
VOL_L += ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
// volume right
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Left])) {
VOL_R -= 64;
VOL_R -= ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
if (GameAPI::Buttons::getState(RI_MGR, buttons[Buttons::VOL_R_Right])) {
VOL_R += 64;
VOL_R += ((uint16_t)DIGITAL_KNOB_SENS * 4);
}
// update volumes
@@ -364,6 +365,7 @@ namespace games::sdvx {
aioNmgrIob2->pAIO_NMGR_IOB_BeginManage = AIO_NMGR_IOB_BeginManageStub;
}
log_info("bi2x_hook", "aioNMgrIob2_Create");
BI2X_INITIALIZED = true;
return &aioNmgrIob2;
}
+123
View File
@@ -10,6 +10,7 @@
#include "hooks/libraryhook.h"
#include "hooks/powrprof.h"
#include "hooks/sleephook.h"
#include "touch/touch.h"
#include "util/detour.h"
#include "util/logging.h"
#include "util/sigscan.h"
@@ -21,11 +22,118 @@
#include "io.h"
#include "acioemu/handle.h"
static decltype(RegCloseKey) *RegCloseKey_orig = nullptr;
static decltype(RegEnumKeyA) *RegEnumKeyA_orig = nullptr;
static decltype(RegOpenKeyA) *RegOpenKeyA_orig = nullptr;
static decltype(RegOpenKeyExA) *RegOpenKeyExA_orig = nullptr;
static decltype(RegQueryValueExA) *RegQueryValueExA_orig = nullptr;
namespace games::sdvx {
// constants
const HKEY PARENT_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x3001);
const HKEY DEVICE_ASIO_REG_HANDLE = reinterpret_cast<HKEY>(0x3002);
const char *ORIGINAL_ASIO_DEVICE_NAME = "XONAR SOUND CARD(64)";
// settings
bool DISABLECAMS = false;
bool NATIVETOUCH = false;
uint8_t DIGITAL_KNOB_SENS = 16;
SdvxOverlayPosition OVERLAY_POS = SDVX_OVERLAY_BOTTOM;
std::optional<std::string> SOUND_OUTPUT_DEVICE = std::nullopt;
std::optional<std::string> ASIO_DRIVER = std::nullopt;
// states
static HKEY real_asio_reg_handle = nullptr;
static HKEY real_asio_device_reg_handle = nullptr;
static LONG WINAPI RegOpenKeyA_hook(HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult) {
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == HKEY_LOCAL_MACHINE &&
ASIO_DRIVER.has_value() &&
_stricmp(lpSubKey, "software\\asio") == 0)
{
*phkResult = PARENT_ASIO_REG_HANDLE;
return RegOpenKeyA_orig(hKey, lpSubKey, &real_asio_reg_handle);
}
}
return RegOpenKeyA_orig(hKey, lpSubKey, phkResult);
}
static LONG WINAPI RegOpenKeyExA_hook(HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired,
PHKEY phkResult)
{
if (lpSubKey != nullptr && phkResult != nullptr) {
if (hKey == PARENT_ASIO_REG_HANDLE &&
ASIO_DRIVER.has_value() &&
_stricmp(lpSubKey, ORIGINAL_ASIO_DEVICE_NAME) == 0)
{
*phkResult = DEVICE_ASIO_REG_HANDLE;
log_info("sdvx::asio", "replacing '{}' with '{}'", lpSubKey, ASIO_DRIVER.value());
return RegOpenKeyExA_orig(real_asio_reg_handle, ASIO_DRIVER.value().c_str(), ulOptions, samDesired,
&real_asio_device_reg_handle);
}
}
return RegOpenKeyExA_orig(hKey, lpSubKey, ulOptions, samDesired, phkResult);
}
static LONG WINAPI RegEnumKeyA_hook(HKEY hKey, DWORD dwIndex, LPSTR lpName, DWORD cchName) {
if (hKey == PARENT_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
if (dwIndex == 0) {
auto ret = RegEnumKeyA_orig(real_asio_reg_handle, dwIndex, lpName, cchName);
if (ret == ERROR_SUCCESS && lpName != nullptr) {
log_info("sdvx::asio", "stubbing '{}' with '{}'", lpName, ORIGINAL_ASIO_DEVICE_NAME);
strncpy(lpName, ORIGINAL_ASIO_DEVICE_NAME, cchName);
}
return ret;
} else {
return ERROR_NO_MORE_ITEMS;
}
}
return RegEnumKeyA_orig(hKey, dwIndex, lpName, cchName);
}
static LONG WINAPI RegQueryValueExA_hook(HKEY hKey, LPCTSTR lpValueName, LPDWORD lpReserved, LPDWORD lpType,
LPBYTE lpData, LPDWORD lpcbData)
{
if (lpValueName != nullptr && lpData != nullptr && lpcbData != nullptr) {
if (hKey == DEVICE_ASIO_REG_HANDLE && ASIO_DRIVER.has_value()) {
log_info("sdvx::asio", "RegQueryValueExA({}, \"{}\")", fmt::ptr((void *) hKey), lpValueName);
if (_stricmp(lpValueName, "Description") == 0) {
// sdvx does a comparison against "XONAR SOUND CARD(64)"
// otherwise you end up with this error:
// M:BMSoundLib: ASIODriver: No such driver: XONAR SOUND CARD(64)
memcpy(lpData, ORIGINAL_ASIO_DEVICE_NAME, strlen(ORIGINAL_ASIO_DEVICE_NAME) + 1);
return ERROR_SUCCESS;
} else {
hKey = real_asio_device_reg_handle;
}
}
}
// fallback
return RegQueryValueExA_orig(hKey, lpValueName, lpReserved, lpType, lpData, lpcbData);
}
static LONG WINAPI RegCloseKey_hook(HKEY hKey) {
if (hKey == PARENT_ASIO_REG_HANDLE || hKey == DEVICE_ASIO_REG_HANDLE) {
return ERROR_SUCCESS;
}
return RegCloseKey_orig(hKey);
}
SDVXGame::SDVXGame() : Game("Sound Voltex") {
}
@@ -161,6 +269,7 @@ namespace games::sdvx {
// hook touch window
if (!NATIVETOUCH) {
wintouchemu::FORCE = true;
wintouchemu::INJECT_MOUSE_AS_WM_TOUCH = true;
wintouchemu::hook_title_ends(
"SOUND VOLTEX",
"Main Screen",
@@ -179,7 +288,21 @@ namespace games::sdvx {
libraryhook_enable(avs::game::DLL_INSTANCE);
}
}
#endif
// ASIO device hook
RegCloseKey_orig = detour::iat_try(
"RegCloseKey", RegCloseKey_hook, avs::game::DLL_INSTANCE);
RegEnumKeyA_orig = detour::iat_try(
"RegEnumKeyA", RegEnumKeyA_hook, avs::game::DLL_INSTANCE);
RegOpenKeyA_orig = detour::iat_try(
"RegOpenKeyA", RegOpenKeyA_hook, avs::game::DLL_INSTANCE);
RegOpenKeyExA_orig = detour::iat_try(
"RegOpenKeyExA", RegOpenKeyExA_hook, avs::game::DLL_INSTANCE);
RegQueryValueExA_orig = detour::iat_try(
"RegQueryValueExA", RegQueryValueExA_hook, avs::game::DLL_INSTANCE);
#ifdef SPICE64
powrprof_hook_init(avs::game::DLL_INSTANCE);
// hook camera
+13
View File
@@ -1,12 +1,25 @@
#pragma once
#include <optional>
#include "games/game.h"
namespace games::sdvx {
enum SdvxOverlayPosition {
SDVX_OVERLAY_TOP,
SDVX_OVERLAY_MIDDLE,
SDVX_OVERLAY_BOTTOM
};
// settings
extern bool DISABLECAMS;
extern bool NATIVETOUCH;
extern uint8_t DIGITAL_KNOB_SENS;
extern std::optional<std::string> ASIO_DRIVER;
extern bool BI2X_INITIALIZED;
extern SdvxOverlayPosition OVERLAY_POS;
class SDVXGame : public games::Game {
public:
+52 -5
View File
@@ -401,23 +401,58 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::EnumAdapterModes(
format2s(pMode->Format));
*/
bool modified = false;
auto width = pMode->Width;
auto height = pMode->Height;
auto refresh = pMode->RefreshRate;
if (Mode == 0 && games::iidx::TDJ_MODE) {
log_misc("graphics::d3d9", "overriding mode 0 to 1280x720 @ 120 Hz");
if (games::iidx::is_tdj_fhd()) {
log_misc("graphics::d3d9", "overriding mode 0 to 1920x1080 @ 120 Hz (for TDJ FHD)");
pMode->Width = 1920;
pMode->Height = 1080;
pMode->RefreshRate = 120;
modified = true;
} else {
log_misc("graphics::d3d9", "overriding mode 0 to 1280x720 @ 120 Hz (for TDJ HD)");
pMode->Width = 1280;
pMode->Height = 720;
pMode->RefreshRate = 120;
modified = true;
}
}
// For whatever reason, TDJ FHD mode prefers to pick these resolutions over 1080p@120Hz.
// Remove them entirely.
if (!modified && games::iidx::is_tdj_fhd() && refresh == 60) {
if ((width == 1920 && height == 1080) ||
(width == 1280 && height == 720)){
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for TDJ FHD)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
}
// zero out display mode for bad entries
// - skip non-native resolutions
// - skip 75 and 90 Hz entries because LDJ game timing is messed up with it
// (TDJ should be fine as it assumes a fixed 60 Hz timing)
if (width == 1360 || width == 1366 || refresh == 75 || refresh == 90) {
if (!modified && (width == 1360 || width == 1366 || refresh == 75 || refresh == 90)) {
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (for LDJ/TDJ)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
if (!modified && !games::iidx::TDJ_MODE && games::iidx::FORCE_720P && (height > 720)) {
log_misc(
"graphics::d3d9", "removing mode {}, {}x{} @ {}Hz (-sp2x-iidxforce720p)",
Mode, width, height, refresh);
memset(pMode, 0, sizeof(*pMode));
modified = true;
}
}
@@ -613,12 +648,20 @@ HRESULT STDMETHODCALLTYPE WrappedIDirect3D9::CreateDevice(
if (GRAPHICS_WINDOWED) {
pPresentationParameters->Windowed = true;
pPresentationParameters->FullScreen_RefreshRateInHz = 0;
} else if (GRAPHICS_FORCE_REFRESH > 0) {
log_info("graphics::d3d9", "force refresh rate: {} => {} Hz",
log_info("graphics::d3d9", "force refresh rate: {} => {} Hz (-graphics-force-refresh option)",
pPresentationParameters->FullScreen_RefreshRateInHz,
GRAPHICS_FORCE_REFRESH);
pPresentationParameters->FullScreen_RefreshRateInHz = GRAPHICS_FORCE_REFRESH;
} else if (pPresentationParameters->FullScreen_RefreshRateInHz == 0) {
log_warning(
"graphics::d3d9",
"This game sets FullScreen_RefreshRateInHz to 0, which means it will boot with whatever "
"refresh rate you have set in the desktop. If the game is launching at the wrong Hz, "
"either use -graphics-force-refresh option or change the desktop resolution beforehand.");
}
// force single adapter
@@ -910,7 +953,11 @@ static void graphics_d3d9_ldj_on_present(IDirect3DDevice9 *wrapped_device) {
HRESULT hr = wrapped_device->GetSwapChain(1, &SUB_SWAP_CHAIN);
if (FAILED(hr)) {
log_warning("graphics::d3d9", "failed to acquire sub swap chain, hr={}", FMT_HRESULT(hr));
log_warning(
"graphics::d3d9",
"failed to acquire sub screeen swap chain; "
"this can be ignored if game does not have sub screen, hr={}",
FMT_HRESULT(hr));
return;
}
}
+30 -10
View File
@@ -716,35 +716,55 @@ void SurfaceHook(IDirect3DDevice9 *pReal) {
const int rectLeft = 1024;
const int rectTop = 576;
const int w = param.BackBufferWidth;
const int h = param.BackBufferHeight;
D3DLOCKED_RECT rect;
RECT targetRect {
rectLeft, rectTop,
(LONG) (rectLeft+param.BackBufferWidth),
(LONG) (rectTop+param.BackBufferHeight),
rectLeft,
rectTop,
(LONG)(rectLeft + w),
(LONG)(rectTop + h),
};
// stretch to add top/left offset to avoid going negative
topSurface->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
auto hr = pReal->StretchRect(backbuffer, nullptr, topSurface,
&targetRect, D3DTEXF_LINEAR);
auto hr = pReal->StretchRect(
backbuffer, nullptr,
topSurface, &targetRect,
D3DTEXF_LINEAR);
if (hr != D3D_OK) {
log_misc("graphics::d3d9", "StretchRect backbuffer failed");
}
topSurface->UnlockRect();
// do the actual zoom / offset math
if (cfg::SCREENRESIZE->centered) {
targetRect.right = (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.bottom = (h + rectTop) / cfg::SCREENRESIZE->scale_y;
const LONG deltaH = ((targetRect.bottom - targetRect.top) - h) / 2;
const LONG deltaW = ((targetRect.right - targetRect.left) - w) / 2;
targetRect.top -= deltaH;
targetRect.bottom -= deltaH;
targetRect.left -= deltaW;
targetRect.right -= deltaW;
} else {
targetRect.left -= cfg::SCREENRESIZE->offset_x;
targetRect.top += cfg::SCREENRESIZE->offset_y;
targetRect.right = -cfg::SCREENRESIZE->offset_x;
targetRect.right += (param.BackBufferWidth+rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.right += (w + rectLeft) / cfg::SCREENRESIZE->scale_x;
targetRect.bottom = cfg::SCREENRESIZE->offset_y;
targetRect.bottom += (param.BackBufferHeight+rectTop) / cfg::SCREENRESIZE->scale_y;
targetRect.bottom += (h + rectTop) / cfg::SCREENRESIZE->scale_y;
}
// draw to back buffer
backbuffer->LockRect(&rect, NULL, D3DLOCK_DONOTWAIT);
hr = pReal->StretchRect(topSurface, &targetRect, backbuffer,
nullptr, cfg::SCREENRESIZE->enable_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
hr = pReal->StretchRect(
topSurface, &targetRect,
backbuffer, nullptr,
cfg::SCREENRESIZE->enable_linear_filter ? D3DTEXF_LINEAR : D3DTEXF_NONE);
backbuffer->UnlockRect();
if (hr != D3D_OK) {
log_misc("graphics::d3d9", "StretchRect targetRect failed");
}
+46 -8
View File
@@ -10,6 +10,7 @@
#include "avs/game.h"
#include "cfg/icon.h"
#include "cfg/screen_resize.h"
#include "games/ddr/ddr.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "launcher/shutdown.h"
#include "overlay/overlay.h"
@@ -18,6 +19,7 @@
#include "util/logging.h"
#include "util/fileutils.h"
#include "util/utils.h"
#include "misc/wintouchemu.h"
#include "util/time.h"
struct CaptureData {
@@ -47,8 +49,8 @@ bool GRAPHICS_CAPTURE_CURSOR = false;
bool GRAPHICS_LOG_HRESULT = false;
bool GRAPHICS_SDVX_FORCE_720 = false;
bool GRAPHICS_SHOW_CURSOR = false;
graphics_orientation GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_NORMAL;
bool GRAPHICS_WINDOWED = false;
bool GRAPHICS_ADJUST_ORIENTATION = false;
std::vector<HWND> GRAPHICS_WINDOWS;
UINT GRAPHICS_FORCE_REFRESH = 0;
bool GRAPHICS_FORCE_SINGLE_ADAPTER = false;
@@ -126,6 +128,24 @@ static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM l
}
}
if (wintouchemu::INJECT_MOUSE_AS_WM_TOUCH) {
// drop mouse inputs since only wintouches should be used
switch (uMsg) {
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONDOWN:
case WM_MBUTTONUP:
case WM_RBUTTONDOWN:
case WM_RBUTTONUP:
case WM_XBUTTONDOWN:
case WM_XBUTTONUP:
return true;
}
}
// window resize
graphics_windowed_wndproc(hWnd, uMsg, wParam, lParam);
// call custom procedures
for (WNDPROC wndProc : WNDPROC_CUSTOM) {
wndProc(hWnd, uMsg, wParam, lParam);
@@ -265,23 +285,35 @@ static HWND WINAPI CreateWindowExA_hook(DWORD dwExStyle, LPCSTR lpClassName, LPC
fmt::ptr(lpParam));
// set display orientation
if (GRAPHICS_ADJUST_ORIENTATION) {
if (nHeight > nWidth) {
// only do this when the target window is portrait
// (avoid doing this for SDVX subscreen which is in landscape, for example)
if ((nHeight > nWidth) &&
(GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW ||
GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CCW)) {
DWORD orientation = (GRAPHICS_ADJUST_ORIENTATION == ORIENTATION_CW) ? DMDO_90 : DMDO_270;
DEVMODE mode {};
// get display settings
if (EnumDisplaySettings(0, ENUM_CURRENT_SETTINGS, &mode)) {
log_misc("graphics", "rotating display");
log_misc("graphics", "auto-rotate: rotating display to DMDO_xx mode {}", orientation);
mode.dmPelsWidth = nWidth;
mode.dmPelsHeight = nHeight;
mode.dmDisplayOrientation = DMDO_90;
mode.dmDisplayOrientation = orientation;
if (GRAPHICS_FORCE_REFRESH > 0) {
log_info(
"graphics",
"auto-rotate: force refresh rate: {} => {} Hz",
mode.dmDisplayFrequency,
GRAPHICS_FORCE_REFRESH);
mode.dmDisplayFrequency = GRAPHICS_FORCE_REFRESH;
}
auto disp_res = ChangeDisplaySettings(&mode, CDS_FULLSCREEN);
if (disp_res != DISP_CHANGE_SUCCESSFUL) {
log_warning("graphics", "failed to change display settings: {}", disp_res);
log_warning("graphics", "auto-rotate: failed to change display settings: {}", disp_res);
}
} else {
log_warning("graphics", "failed to get display settings");
}
log_warning("graphics", "auto-rotate: failed to get display settings");
}
}
@@ -328,7 +360,11 @@ static HWND WINAPI CreateWindowExW_hook(DWORD dwExStyle, LPCWSTR lpClassName, LP
// adjust window size to include window decoration
RECT rect {};
if (games::ddr::SDMODE) {
SetRect(&rect, 0, 0, 800, 600);
} else {
SetRect(&rect, 0, 0, 1280, 720);
}
AdjustWindowRect(&rect, dwStyle, (hMenu != nullptr));
nWidth = rect.right - rect.left;
@@ -683,6 +719,8 @@ void graphics_hook_window(HWND hWnd, D3DPRESENT_PARAMETERS *pPresentationParamet
if (WNDPROC_ORIG == nullptr) {
WNDPROC_ORIG = reinterpret_cast<WNDPROC>(GetWindowLongPtrA(hWnd, GWLP_WNDPROC));
SetWindowLongPtrA(hWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WindowProc));
overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = true;
graphics_capture_initial_window(hWnd);
}
}
+21 -1
View File
@@ -9,18 +9,30 @@
#include "external/toojpeg/toojpeg.h"
// order must match spice2x_AutoOrientation UI enum order
enum graphics_orientation {
ORIENTATION_CW = 0,
ORIENTATION_CCW = 1,
ORIENTATION_NORMAL = 2,
};
// flag settings
extern bool GRAPHICS_CAPTURE_CURSOR;
extern bool GRAPHICS_LOG_HRESULT;
extern bool GRAPHICS_SDVX_FORCE_720;
extern bool GRAPHICS_SHOW_CURSOR;
extern bool GRAPHICS_WINDOWED;
extern bool GRAPHICS_ADJUST_ORIENTATION;
extern graphics_orientation GRAPHICS_ADJUST_ORIENTATION;
extern std::vector<HWND> GRAPHICS_WINDOWS;
extern UINT GRAPHICS_FORCE_REFRESH;
extern bool GRAPHICS_FORCE_SINGLE_ADAPTER;
extern bool GRAPHICS_9_ON_12;
extern std::optional<int> GRAPHICS_WINDOW_STYLE;
extern std::optional<std::string> GRAPHICS_WINDOW_SIZE;
extern std::optional<std::string> GRAPHICS_WINDOW_POS;
extern bool GRAPHICS_WINDOW_ALWAYS_ON_TOP;
// settings
extern std::string GRAPHICS_DEVICEID;
extern std::string GRAPHICS_SCREENSHOT_DIR;
@@ -47,3 +59,11 @@ bool graphics_capture_receive_jpeg(int screen, TooJpeg::WRITE_ONE_BYTE receiver,
uint64_t *timestamp = nullptr,
int *width = nullptr, int *height = nullptr);
std::string graphics_screenshot_genpath();
// graphics_windowed.cpp
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void graphics_capture_initial_window(HWND hWnd);
void graphics_update_window_style(HWND hWnd);
void graphics_update_z_order(HWND hWnd);
void graphics_move_resize_window(HWND hWnd);
bool graphics_window_change_crashes_game();
+431
View File
@@ -0,0 +1,431 @@
#include "graphics.h"
#include "avs/game.h"
#include "cfg/screen_resize.h"
#include "overlay/overlay.h"
#include "util/logging.h"
#include "util/utils.h"
#if 0
#define log_debug(module, format_str, ...) logger::push( \
LOG_FORMAT("M", module, format_str, ## __VA_ARGS__), logger::Style::GREY)
#else
#define log_debug(module, format_str, ...)
#endif
void graphics_load_windowed_parameters();
void graphics_wm_style_changed(HWND hWnd, bool changed);
void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect);
std::optional<int> GRAPHICS_WINDOW_STYLE;
std::optional<std::string> GRAPHICS_WINDOW_SIZE;
std::optional<std::string> GRAPHICS_WINDOW_POS;
bool GRAPHICS_WINDOW_ALWAYS_ON_TOP = false;
// these flags are carefully constructed to ensure maximum compatibility
// (e.g., DDR likes to hang when SetWindowPos is called with certain params)
static const DWORD SETWINDOWPOS_NOOP =
SWP_NOMOVE |
SWP_NOSIZE |
SWP_NOREDRAW |
SWP_NOCOPYBITS |
SWP_NOACTIVATE |
SWP_NOSENDCHANGING |
SWP_DEFERERASE |
SWP_NOZORDER |
SWP_ASYNCWINDOWPOS;
void graphics_capture_initial_window(HWND hWnd) {
if (!GRAPHICS_WINDOWED) {
return;
}
graphics_load_windowed_parameters();
cfg::SCREENRESIZE->init_window_style = GetWindowLong(hWnd, GWL_STYLE);
log_debug("graphics-windowed", "graphics_capture_initial_window called");
RECT rect;
if (!GetClientRect(hWnd, &rect)) {
log_warning(
"graphics",
"graphics_capture_initial_window - GetClientRect failed, GLE: {}",
GetLastError());
return;
}
const int client_w = rect.right - rect.left;
const int client_h = rect.bottom - rect.top;
cfg::SCREENRESIZE->init_client_width = client_w;
cfg::SCREENRESIZE->init_client_height = client_h;
cfg::SCREENRESIZE->init_client_aspect_ratio = (float)client_w / (float)client_h;
log_debug(
"graphics-windowed",
"graphics_capture_initial_window initial window size {}x{}, ratio {}",
client_w, client_h, cfg::SCREENRESIZE->init_client_aspect_ratio);
// ensure frame size is captured
graphics_wm_style_changed(hWnd, false);
// if there was no user-supplied dimension, seed it with the current size
// so that the next resize operation will work
if (cfg::SCREENRESIZE->client_width == 0) {
cfg::SCREENRESIZE->client_width = client_w;
}
if (cfg::SCREENRESIZE->client_height == 0) {
cfg::SCREENRESIZE->client_height = client_h;
}
// apply the config that was loaded from disk
// resize must be done before applying the border
if (cfg::SCREENRESIZE->enable_window_resize) {
log_info(
"graphics-windowed", "change window rect - window offset: {}x{}, client size: {}x{}",
cfg::SCREENRESIZE->window_offset_x,
cfg::SCREENRESIZE->window_offset_y,
cfg::SCREENRESIZE->client_width,
cfg::SCREENRESIZE->client_height);
graphics_move_resize_window(hWnd);
}
// ddr hangs when window frame doesn't have overlapped
if (cfg::SCREENRESIZE->window_decoration != cfg::WindowDecorationMode::Default) {
log_info(
"graphics-windowed", "change window style - decoration: {}",
cfg::SCREENRESIZE->window_decoration);
graphics_update_window_style(hWnd);
}
if (cfg::SCREENRESIZE->window_always_on_top) {
log_info("graphics-windowed", "change window z-order - always on top");
graphics_update_z_order(hWnd);
}
log_debug("graphics-windowed", "graphics_capture_initial_window returned");
}
void graphics_load_windowed_parameters() {
if (!GRAPHICS_WINDOWED) {
return;
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters called");
const auto remove_spaces = [](const char& c) {
return c == ' ';
};
if (GRAPHICS_WINDOW_STYLE.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_STYLE");
cfg::SCREENRESIZE->window_decoration = GRAPHICS_WINDOW_STYLE.value();
}
if (GRAPHICS_WINDOW_SIZE.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_SIZE");
uint32_t w, h;
auto s = GRAPHICS_WINDOW_SIZE.value();
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
if (sscanf(s.c_str(), "%u,%u", &w, &h) == 2) {
cfg::SCREENRESIZE->enable_window_resize = true;
cfg::SCREENRESIZE->client_keep_aspect_ratio = false;
cfg::SCREENRESIZE->client_width = w;
cfg::SCREENRESIZE->client_height = h;
} else {
log_warning("graphics-windowed", "failed to parse -sp2x-windowsize");
}
}
if (GRAPHICS_WINDOW_POS.has_value()) {
log_debug(
"graphics-windowed",
"graphics_load_windowed_parameters - load GRAPHICS_WINDOW_POS");
int32_t x, y;
auto s = GRAPHICS_WINDOW_POS.value();
s.erase(std::remove_if(s.begin(), s.end(), remove_spaces), s.end());
if (sscanf(s.c_str(), "%d,%d", &x, &y) == 2) {
cfg::SCREENRESIZE->enable_window_resize = true;
cfg::SCREENRESIZE->window_offset_x = x;
cfg::SCREENRESIZE->window_offset_y = y;
} else {
log_warning("graphics-windowed", "failed to parse -sp2x-windowpos");
}
}
// only override if true; don't stomp on user setting
if (GRAPHICS_WINDOW_ALWAYS_ON_TOP) {
cfg::SCREENRESIZE->window_always_on_top = true;
}
log_debug("graphics-windowed", "graphics_load_windowed_parameters returned");
}
void graphics_windowed_wndproc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (!GRAPHICS_WINDOWED) {
return;
}
switch (uMsg) {
case WM_MOVE: {
log_debug("graphics-windowed", "graphics_windowed_wndproc called with WM_MOVE");
RECT rect;
if (GetWindowRect(hWnd, &rect)) {
cfg::SCREENRESIZE->window_offset_x = rect.left;
cfg::SCREENRESIZE->window_offset_y = rect.top;
}
break;
}
case WM_SIZE: {
log_debug("graphics-windowed", "graphics_windowed_wndproc called with WM_SIZE");
if (wParam == SIZE_MINIMIZED) {
break;
}
RECT rect;
if (GetClientRect(hWnd, &rect)) {
cfg::SCREENRESIZE->client_width = rect.right - rect.left;
cfg::SCREENRESIZE->client_height = rect.bottom - rect.top;
}
break;
}
case WM_SIZING: {
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
graphics_wm_sizing_aspect_ratio(
static_cast<int>(wParam), *reinterpret_cast<LPRECT>(lParam));
}
break;
}
case WM_STYLECHANGED: {
graphics_wm_style_changed(hWnd, true);
break;
}
case WM_GETMINMAXINFO: {
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
auto info = reinterpret_cast<MINMAXINFO *>(lParam);
info->ptMinTrackSize.y =
cfg::SCREENRESIZE->window_deco_height +
((info->ptMinTrackSize.x - cfg::SCREENRESIZE->window_deco_width) /
cfg::SCREENRESIZE->init_client_aspect_ratio);
}
break;
}
}
}
void graphics_wm_style_changed(HWND hWnd, bool changed) {
log_debug("graphics-windowed", "graphics_wm_style_changed called");
RECT rect;
// ensure the style change takes in effect before doing the calculations
if (changed) {
// ensure client size doesn't change as a result of this
// since SetWindowPos will still send WM_SIZE and WM_MOVE
const auto client_w = cfg::SCREENRESIZE->client_width;
const auto client_h = cfg::SCREENRESIZE->client_height;
const auto flags = SETWINDOWPOS_NOOP | SWP_FRAMECHANGED;
SetWindowPos(
hWnd,
nullptr,
0, 0, 0, 0,
flags);
cfg::SCREENRESIZE->client_width = client_w;
cfg::SCREENRESIZE->client_height = client_h;
}
// get window size with decoration...
if (!GetWindowRect(hWnd, &rect)) {
log_warning(
"graphics",
"graphics_wm_style_changed - GetWindowRect failed, GLE: {}",
GetLastError());
return;
}
const int window_w = rect.right - rect.left;
const int window_h = rect.bottom - rect.top;
// get client area (without decoration)...
if (!GetClientRect(hWnd, &rect)) {
return;
}
const int client_w = rect.right - rect.left;
const int client_h = rect.bottom - rect.top;
// update window decoration size
cfg::SCREENRESIZE->window_deco_width = window_w - client_w;
cfg::SCREENRESIZE->window_deco_height = window_h - client_h;
log_debug(
"graphics-windowed",
"graphics_wm_style_changed updating frame dimensions {}x{}",
cfg::SCREENRESIZE->window_deco_width,
cfg::SCREENRESIZE->window_deco_height);
// adjust window to ensure client area remains the same
if (changed) {
graphics_move_resize_window(hWnd);
}
log_debug("graphics-windowed", "graphics_wm_style_changed returned");
}
void graphics_wm_sizing_aspect_ratio(int edge, RECT& rect) {
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio called");
const auto deco_w = cfg::SCREENRESIZE->window_deco_width;
const auto deco_h = cfg::SCREENRESIZE->window_deco_height;
const LONG desired_w = (rect.right - rect.left) - deco_w;
const LONG desired_h = (rect.bottom - rect.top) - deco_h;
const auto aspect_ratio = cfg::SCREENRESIZE->init_client_aspect_ratio;
// based on http://playtechs.blogspot.com/2007/10/forcing-window-to-maintain-particular.html
switch (edge) {
case WMSZ_BOTTOM:
case WMSZ_TOP: {
const LONG w = deco_w + (desired_h * aspect_ratio);
rect.right = rect.left + w;
break;
}
case WMSZ_LEFT:
case WMSZ_RIGHT: {
const LONG h = deco_h + (desired_w / aspect_ratio);
rect.bottom = rect.top + h;
break;
}
case WMSZ_TOPLEFT:
case WMSZ_TOPRIGHT:
case WMSZ_BOTTOMLEFT:
case WMSZ_BOTTOMRIGHT: {
int w;
int h;
if (desired_h * aspect_ratio < desired_w) {
w = rect.right - rect.left;
h = deco_h + (desired_w / aspect_ratio);
} else {
w = deco_w + (desired_h * aspect_ratio);
h = rect.bottom - rect.top;
}
if (edge == WMSZ_TOPLEFT) {
rect.left = rect.right - w;
rect.top = rect.bottom - h;
} else if (edge == WMSZ_TOPRIGHT) {
rect.right = rect.left + w;
rect.top = rect.bottom - h;
} else if (edge == WMSZ_BOTTOMLEFT) {
rect.left = rect.right - w;
rect.bottom = rect.top + h;
} else if (edge == WMSZ_BOTTOMRIGHT) {
rect.right = rect.left + w;
rect.bottom = rect.top + h;
}
break;
}
default:
break;
}
log_debug("graphics-windowed", "graphics_wm_sizing_aspect_ratio returned");
}
void graphics_update_window_style(HWND hWnd) {
if (!GRAPHICS_WINDOWED) {
return;
}
if (graphics_window_change_crashes_game()) {
return;
}
log_debug("graphics-windowed", "graphics_update_window_style called");
// update frame style
auto style = cfg::SCREENRESIZE->init_window_style;
switch (cfg::SCREENRESIZE->window_decoration) {
case cfg::WindowDecorationMode::Borderless:
style &= ~WS_OVERLAPPEDWINDOW;
break;
case cfg::WindowDecorationMode::ResizableFrame:
style |= WS_OVERLAPPEDWINDOW;
break;
case cfg::WindowDecorationMode::Default:
default:
break;
}
log_debug(
"graphics-windowed",
"graphics_update_window_style - calling SetWindowLong with Mode {}, style 0x{:x}",
static_cast<int>(cfg::SCREENRESIZE->window_decoration),
style);
SetWindowLong(hWnd, GWL_STYLE, style);
// SetWindowPos must be called after SetWindowLong if the frame style changed
// this will be done in WM_STYLECHANGED handler
log_debug("graphics-windowed", "graphics_update_window_style returned");
}
void graphics_update_z_order(HWND hWnd) {
if (!GRAPHICS_WINDOWED) {
return;
}
log_debug("graphics-windowed", "graphics_update_z_order called");
HWND insert_after = nullptr;
if (cfg::SCREENRESIZE->window_always_on_top) {
insert_after = HWND_TOPMOST;
} else {
insert_after = HWND_NOTOPMOST;
}
auto flags = SETWINDOWPOS_NOOP;
flags &= ~SWP_NOZORDER;
SetWindowPos(
hWnd,
insert_after,
0, 0, 0, 0,
flags);
log_debug("graphics-windowed", "graphics_update_z_order returned");
}
void graphics_move_resize_window(HWND hWnd) {
if (!GRAPHICS_WINDOWED) {
return;
}
log_debug("graphics-windowed", "graphics_move_resize_window called");
cfg::SCREENRESIZE->client_width =
CLAMP(cfg::SCREENRESIZE->client_width, 640, 1920 * 8);
cfg::SCREENRESIZE->client_height =
CLAMP(cfg::SCREENRESIZE->client_height, 480, 1080 * 8);
const auto w = cfg::SCREENRESIZE->client_width + cfg::SCREENRESIZE->window_deco_width;
const auto h = cfg::SCREENRESIZE->client_height + cfg::SCREENRESIZE->window_deco_height;
auto flags = SETWINDOWPOS_NOOP;
flags &= ~SWP_NOSIZE;
flags &= ~SWP_NOMOVE;
SetWindowPos(
hWnd,
nullptr,
cfg::SCREENRESIZE->window_offset_x,
cfg::SCREENRESIZE->window_offset_y,
w, h,
flags);
log_debug("graphics-windowed", "graphics_move_resize_window returned");
}
bool graphics_window_change_crashes_game() {
static std::once_flag flag;
static bool result = false;
std::call_once(flag, []() {
// ddr crashes when frame style changes
result = avs::game::is_model("MDX");
if (result) {
log_warning(
"graphics-windowed",
"ignoring changes to window style due to incompatibility with this game");
}
});
return result;
}
+200 -44
View File
@@ -78,6 +78,7 @@
#include "misc/wintouchemu.h"
#include "overlay/overlay.h"
#include "overlay/windows/patch_manager.h"
#include "overlay/windows/iidx_seg.h"
#include "rawinput/rawinput.h"
#include "rawinput/touch.h"
#include "reader/reader.h"
@@ -203,7 +204,7 @@ int main_implementation(int argc, char *argv[]) {
bool load_stubs = false;
bool netfix_disable = false;
bool lang_disable = false;
bool realtime = false;
std::string process_priority_str = "high";
bool cardio_enabled = false;
bool peb_print = false;
bool cfg_run = false;
@@ -258,18 +259,20 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::ConfigurationPath].is_active()) {
CONFIG_PATH_OVERRIDE = options[launcher::Options::ConfigurationPath].value_text();
}
if (options[launcher::Options::EAmusementEmulation].value_bool()
&& (options[launcher::Options::ServiceURL].is_active())) {
log_warning("launcher", "-------------------------------------------------------------------");
log_warning("launcher", "WARNING - WARNING - WARNING - WARNING - WARNING - WARNING - WARNING");
log_warning("launcher", "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
log_warning("launcher", "A service URL is set **AND** E-Amusement emulation is enabled.");
log_warning("launcher", "Either remove the service URL, or disable E-Amusement emulation.");
log_warning("launcher", "Otherwise you may experience problems logging in.");
log_warning("launcher", "-------------------------------------------------------------------");
if (!cfg::CONFIGURATOR_STANDALONE) {
Sleep(3000);
}
if (options[launcher::Options::EAmusementEmulation].value_bool() &&
options[launcher::Options::ServiceURL].is_active() &&
!cfg::CONFIGURATOR_STANDALONE) {
log_fatal(
"launcher",
"BAD EAMUSE SETTINGS ERROR\n\n\n"
"-------------------------------------------------------------------\n"
"WARNING - WARNING - WARNING - WARNING - WARNING - WARNING - WARNING\n"
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
"A service URL is set **AND** E-Amusement emulation is enabled.\n"
"Either remove the service URL, or disable E-Amusement emulation.\n"
"Otherwise you may experience problems logging in.\n"
"-------------------------------------------------------------------\n\n\n"
);
}
if (options[launcher::Options::EAmusementEmulation].value_bool()) {
avs::ea3::URL_SLASH = 1;
@@ -279,19 +282,19 @@ int main_implementation(int argc, char *argv[]) {
easrv_smart = true;
}
if (options[launcher::Options::EAmusementMaintenance].is_active()) {
easrv_maint = options[launcher::Options::EAmusementMaintenance].value_int() > 0;
easrv_maint = options[launcher::Options::EAmusementMaintenance].value_uint32() > 0;
}
if (options[launcher::Options::WindowedMode].value_bool()) {
GRAPHICS_WINDOWED = true;
}
if (options[launcher::Options::GraphicsForceRefresh].is_active()) {
GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_int();
GRAPHICS_FORCE_REFRESH = options[launcher::Options::GraphicsForceRefresh].value_uint32();
}
if (options[launcher::Options::GraphicsForceSingleAdapter].value_bool()) {
GRAPHICS_FORCE_SINGLE_ADAPTER = true;
}
if (options[launcher::Options::DisplayAdapter].is_active()) {
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_int();
D3D9_ADAPTER = options[launcher::Options::DisplayAdapter].value_uint32();
}
if (options[launcher::Options::CaptureCursor].value_bool()) {
GRAPHICS_CAPTURE_CURSOR = true;
@@ -305,14 +308,22 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::VerboseAVSLogging].value_bool()) {
hooks::avs::config::LOG = true;
}
if (options[launcher::Options::AdjustOrientation].value_bool()) {
GRAPHICS_ADJUST_ORIENTATION = true;
if (options[launcher::Options::spice2x_AutoOrientation].is_active()) {
GRAPHICS_ADJUST_ORIENTATION =
(graphics_orientation)options[launcher::Options::spice2x_AutoOrientation].value_uint32();
} else if (options[launcher::Options::AdjustOrientation].value_bool()) {
GRAPHICS_ADJUST_ORIENTATION = ORIENTATION_CW;
}
if (options[launcher::Options::LogLevel].is_active()) {
avs::core::LOG_LEVEL_CUSTOM = options[launcher::Options::LogLevel].value_text();
}
for (auto &hook : options[launcher::Options::InjectHook].values_text()) {
game_hooks.push_back(hook);
std::string buffer;
std::stringstream stream(hook);
std::vector<std::string> tokens;
while (stream >> buffer) {
game_hooks.push_back(buffer);
}
}
if (options[launcher::Options::LoadStubs].value_bool()) {
load_stubs = true;
@@ -347,6 +358,21 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::SDVXNativeTouch].value_bool()) {
games::sdvx::NATIVETOUCH = true;
}
if (options[launcher::Options::spice2x_SDVXDigitalKnobSensitivity].is_active()) {
games::sdvx::DIGITAL_KNOB_SENS = (uint8_t)
options[launcher::Options::spice2x_SDVXDigitalKnobSensitivity].value_uint32();
}
if (options[launcher::Options::spice2x_SDVXAsioDriver].is_active()) {
games::sdvx::ASIO_DRIVER = options[launcher::Options::spice2x_SDVXAsioDriver].value_text();
}
if (options[launcher::Options::spice2x_SDVXSubPos].is_active()) {
auto txt = options[launcher::Options::spice2x_SDVXSubPos].value_text();
if (txt == "top") {
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_TOP;
} else if (txt == "center") {
games::sdvx::OVERLAY_POS = games::sdvx::SDVX_OVERLAY_MIDDLE;
}
}
if (options[launcher::Options::LoadIIDXModule].value_bool()) {
attach_iidx = true;
}
@@ -365,6 +391,29 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::IIDXTDJMode].value_bool()) {
games::iidx::TDJ_MODE = true;
}
if (options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].is_active()) {
games::iidx::DIGITAL_TT_SENS = (uint8_t)
options[launcher::Options::spice2x_IIDXDigitalTTSensitivity].value_uint32();
}
if (options[launcher::Options::spice2x_IIDXLDJForce720p].value_bool()) {
games::iidx::FORCE_720P = true;
}
if (options[launcher::Options::spice2x_IIDXTDJSubSize].is_active()) {
games::iidx::SUBSCREEN_OVERLAY_SIZE =
options[launcher::Options::spice2x_IIDXTDJSubSize].value_text();
}
if (options[launcher::Options::spice2x_IIDXLEDFontSize].is_active()) {
overlay::windows::IIDX_SEGMENT_FONT_SIZE =
options[launcher::Options::spice2x_IIDXLEDFontSize].value_uint32();
}
if (options[launcher::Options::spice2x_IIDXLEDColor].is_active()) {
overlay::windows::IIDX_SEGMENT_FONT_COLOR =
options[launcher::Options::spice2x_IIDXLEDColor].value_hex64();
}
if (options[launcher::Options::spice2x_IIDXLEDPos].is_active()) {
overlay::windows::IIDX_SEGMENT_LOCATION =
options[launcher::Options::spice2x_IIDXLEDPos].value_text();
}
if (options[launcher::Options::LoadJubeatModule].value_bool()) {
attach_jb = true;
}
@@ -393,7 +442,7 @@ int main_implementation(int argc, char *argv[]) {
attach_gitadora = true;
}
if (options[launcher::Options::GitaDoraCabinetType].is_active()) {
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_int();
games::gitadora::CAB_TYPE = options[launcher::Options::GitaDoraCabinetType].value_uint32();
}
if (options[launcher::Options::LoadNostalgiaModule].value_bool()) {
attach_nostalgia = true;
@@ -492,25 +541,27 @@ int main_implementation(int argc, char *argv[]) {
games::shared::PRINTER_FORMAT.push_back(path);
}
if (options[launcher::Options::SDVXPrinterJPGQuality].is_active()) {
games::shared::PRINTER_JPG_QUALITY = options[launcher::Options::SDVXPrinterJPGQuality].value_int();
games::shared::PRINTER_JPG_QUALITY = options[launcher::Options::SDVXPrinterJPGQuality].value_uint32();
}
if (options[launcher::Options::SDVXPrinterOutputClear].value_bool()) {
games::shared::PRINTER_CLEAR = true;
}
if (options[launcher::Options::HTTP11].is_active()) {
avs::ea3::HTTP11 = options[launcher::Options::HTTP11].value_int();
avs::ea3::HTTP11 = options[launcher::Options::HTTP11].value_uint32();
}
if (options[launcher::Options::DisableSSL].value_bool()) {
ssl_disable = true;
}
if (options[launcher::Options::URLSlash].is_active()) {
avs::ea3::URL_SLASH = options[launcher::Options::URLSlash].value_int();
avs::ea3::URL_SLASH = options[launcher::Options::URLSlash].value_uint32();
}
if (options[launcher::Options::RealtimeProcessPriority].value_bool()) {
realtime = true;
if (options[launcher::Options::spice2x_ProcessPriority].is_active()) {
process_priority_str = options[launcher::Options::spice2x_ProcessPriority].value_text();
} else if (options[launcher::Options::RealtimeProcessPriority].value_bool()) {
process_priority_str = "realtime";
}
if (options[launcher::Options::HeapSize].is_active()) {
user_heap_size = options[launcher::Options::HeapSize].value_int();
user_heap_size = options[launcher::Options::HeapSize].value_uint32();
}
if (options[launcher::Options::DisableAvsVfsDriveMountRedirection].is_active()) {
hooks::avs::config::DISABLE_VFS_DRIVE_REDIRECTION = true;
@@ -603,7 +654,7 @@ int main_implementation(int argc, char *argv[]) {
}
if (options[launcher::Options::APITCPPort].is_active()) {
api_enable = true;
api_port = options[launcher::Options::APITCPPort].value_int();
api_port = options[launcher::Options::APITCPPort].value_uint32();
}
if (options[launcher::Options::APIPassword].is_active()) {
api_pass = options[launcher::Options::APIPassword].value_text();
@@ -612,7 +663,7 @@ int main_implementation(int argc, char *argv[]) {
api_serial_port.push_back(options[launcher::Options::APISerialPort].value_text());
}
if (options[launcher::Options::APISerialBaud].is_active()) {
api_serial_baud.push_back(options[launcher::Options::APISerialBaud].value_int());
api_serial_baud.push_back(options[launcher::Options::APISerialBaud].value_uint32());
}
if (options[launcher::Options::APIPretty].value_bool()) {
api_pretty = true;
@@ -675,7 +726,7 @@ int main_implementation(int argc, char *argv[]) {
hooks::audio::BACKEND = backend;
}
if (options[launcher::Options::AsioDriverId].is_active()) {
hooks::audio::ASIO_DRIVER_ID = options[launcher::Options::AsioDriverId].value_int();
hooks::audio::ASIO_DRIVER_ID = options[launcher::Options::AsioDriverId].value_uint32();
}
if (options[launcher::Options::AudioDummy].value_bool()) {
hooks::audio::USE_DUMMY = true;
@@ -699,6 +750,51 @@ int main_implementation(int argc, char *argv[]) {
if (options[launcher::Options::GameExecutable].is_active()) {
avs::game::DLL_NAME = options[launcher::Options::GameExecutable].value_text();
}
if (options[launcher::Options::spice2x_LightsOverallBrightness].is_active()) {
rawinput::HID_LIGHT_BRIGHTNESS =
(uint8_t)options[launcher::Options::spice2x_LightsOverallBrightness].value_uint32();
}
if (options[launcher::Options::spice2x_FpsAutoShow].value_bool()) {
overlay::AUTO_SHOW_FPS = true;
}
if (options[launcher::Options::spice2x_SubScreenAutoShow].value_bool()) {
overlay::AUTO_SHOW_SUBSCREEN = true;
}
if (options[launcher::Options::spice2x_IOPanelAutoShow].value_bool()) {
overlay::AUTO_SHOW_IOPANEL = true;
}
if (options[launcher::Options::spice2x_KeypadAutoShow].is_active()) {
auto s = options[launcher::Options::spice2x_KeypadAutoShow].value_uint32();
switch (s) {
case 1:
overlay::AUTO_SHOW_KEYPAD_P1 = true;
break;
case 2:
overlay::AUTO_SHOW_KEYPAD_P2 = true;
break;
case 3:
overlay::AUTO_SHOW_KEYPAD_P1 = true;
overlay::AUTO_SHOW_KEYPAD_P2 = true;
break;
}
}
if (options[launcher::Options::spice2x_WindowBorder].is_active()) {
GRAPHICS_WINDOW_STYLE = options[launcher::Options::spice2x_WindowBorder].value_uint32();
}
if (options[launcher::Options::spice2x_WindowSize].is_active()) {
GRAPHICS_WINDOW_SIZE = options[launcher::Options::spice2x_WindowSize].value_text();
}
if (options[launcher::Options::spice2x_WindowPosition].is_active()) {
GRAPHICS_WINDOW_POS = options[launcher::Options::spice2x_WindowPosition].value_text();
}
GRAPHICS_WINDOW_ALWAYS_ON_TOP = options[launcher::Options::spice2x_WindowAlwaysOnTop].value_bool();
if (options[launcher::Options::spice2x_JubeatLegacyTouch].value_bool()) {
games::jb::TOUCH_LEGACY_BOX = true;
}
if (options[launcher::Options::spice2x_RBTouchScale].is_active()) {
games::rb::TOUCH_SCALING = options[launcher::Options::spice2x_RBTouchScale].value_uint32();
}
// API debugging
if (api_debug && !cfg::CONFIGURATOR_STANDALONE) {
@@ -717,23 +813,32 @@ int main_implementation(int argc, char *argv[]) {
}
// delay
if (!cfg::CONFIGURATOR_STANDALONE &&
(options[launcher::Options::DelayBy5Seconds].value_bool()))
{
log_info("launcher", "delay by 5000ms...");
Sleep(5000);
if (!cfg::CONFIGURATOR_STANDALONE) {
DWORD delayInSeconds = 0;
if (options[launcher::Options::spice2x_DelayByNSeconds].is_active()) {
delayInSeconds = (DWORD)options[launcher::Options::spice2x_DelayByNSeconds].value_uint32();
} else if (options[launcher::Options::DelayBy5Seconds].value_bool()) {
delayInSeconds = 5;
}
if (0 < delayInSeconds) {
log_info("launcher", "delay by {}ms...", delayInSeconds * 1000);
Sleep(delayInSeconds * 1000);
}
}
// create log file
// configurator does not write a log file because it tends to cause the
// config file to be corrupt...
if (!cfg::CONFIGURATOR_STANDALONE) {
avs::core::create_log();
}
// log
#ifdef SPICE64
log_info("launcher", "SpiceTools Bootstrap (x64)");
log_info("launcher", "SpiceTools Bootstrap (x64) (spice2x)");
#else
log_info("launcher", "SpiceTools Bootstrap (x32)");
log_info("launcher", "SpiceTools Bootstrap (x32) (spice2x)");
#endif
log_info("launcher", "{}", VERSION_STRING);
@@ -772,13 +877,64 @@ int main_implementation(int argc, char *argv[]) {
}
// set process priority
if (!cfg::CONFIGURATOR_STANDALONE) {
DWORD process_priority = HIGH_PRIORITY_CLASS;
if (process_priority_str == "belownormal") {
process_priority = BELOW_NORMAL_PRIORITY_CLASS;
} else if (process_priority_str == "normal") {
process_priority = NORMAL_PRIORITY_CLASS;
} else if (process_priority_str == "abovenormal") {
process_priority = ABOVE_NORMAL_PRIORITY_CLASS;
// high is the default so it's skipped!
} else if (process_priority_str == "realtime") {
process_priority = REALTIME_PRIORITY_CLASS;
}
// while testing, realtime only worked when being set to high before
if (process_priority == REALTIME_PRIORITY_CLASS) {
if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) {
log_warning("launcher", "could not set process priority to high: {}", GetLastError());
log_warning(
"launcher",
"could not set process priority to high, GLE:{}",
GetLastError());
}
}
if (!SetPriorityClass(GetCurrentProcess(), process_priority)) {
log_warning(
"launcher",
"could not set process priority to {}, GLE:{}",
process_priority_str, GetLastError());
} else {
log_info(
"launcher",
"SetPriorityClass succeeded, set priority to {}",
process_priority_str);
}
}
// set process affinity
if (!cfg::CONFIGURATOR_STANDALONE &&
options[launcher::Options::spice2x_ProcessAffinity].is_active()) {
uint64_t affinity = options[launcher::Options::spice2x_ProcessAffinity].value_hex64();
if (SetProcessAffinityMask(GetCurrentProcess(), affinity) != 0) {
log_info(
"launcher",
"SetProcessAffinityMask succeeded, affinity set to 0x{:x}",
affinity);
} else {
auto gle = GetLastError();
if (gle == ERROR_INVALID_PARAMETER) {
log_fatal(
"launcher",
"SetProcessAffinityMask failed, provided 0x{:x}, GLE: ERROR_INVALID_PARAMETER. "
"Make sure you only specify bits for processors that exist",
affinity);
} else {
log_fatal(
"launcher",
"SetProcessAffinityMask failed, provided 0x{:x}, GLE: {}",
affinity,
gle);
}
if (realtime) {
// while testing, realtime only worked when being set to high before, so this is fine
if (!SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS)) {
log_warning("launcher", "could not set process priority to realtime: {}", GetLastError());
}
}
@@ -1445,9 +1601,6 @@ int main_implementation(int argc, char *argv[]) {
}
}
// load AVS-EA3
avs::ea3::boot(easrv_port, easrv_maint, easrv_smart);
// apply patches
{
overlay::windows::PatchManager patch_manager(nullptr, true);
@@ -1457,6 +1610,9 @@ int main_implementation(int argc, char *argv[]) {
script::manager_scan();
script::manager_boot();
// load AVS-EA3
avs::ea3::boot(easrv_port, easrv_maint, easrv_smart);
// eamuse init
eamuse_autodetect_game();
+2
View File
@@ -148,6 +148,8 @@ namespace logger {
THREAD = new std::thread([] {
std::unique_lock<std::mutex> lock(EVENT_MUTEX);
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
// main loop
while (RUNNING) {
+488 -118
View File
File diff suppressed because it is too large Load Diff
+26 -1
View File
@@ -39,6 +39,11 @@ namespace launcher {
URLSlash,
SOFTID,
VREnable,
DisableOverlay,
spice2x_FpsAutoShow,
spice2x_SubScreenAutoShow,
spice2x_IOPanelAutoShow,
spice2x_KeypadAutoShow,
LoadIIDXModule,
IIDXCameraOrderFlip,
IIDXDisableCameras,
@@ -46,6 +51,12 @@ namespace launcher {
IIDXAsioDriver,
IIDXBIO2FW,
IIDXTDJMode,
spice2x_IIDXDigitalTTSensitivity,
spice2x_IIDXLDJForce720p,
spice2x_IIDXTDJSubSize,
spice2x_IIDXLEDFontSize,
spice2x_IIDXLEDColor,
spice2x_IIDXLEDPos,
LoadSoundVoltexModule,
SDVXForce720p,
SDVXPrinterEmulation,
@@ -56,6 +67,9 @@ namespace launcher {
SDVXPrinterJPGQuality,
SDVXDisableCameras,
SDVXNativeTouch,
spice2x_SDVXDigitalKnobSensitivity,
spice2x_SDVXAsioDriver,
spice2x_SDVXSubPos,
LoadDDRModule,
DDR43Mode,
LoadPopnMusicModule,
@@ -123,16 +137,19 @@ namespace launcher {
SextetStreamPort,
EnableBemaniTools5API,
RealtimeProcessPriority,
spice2x_ProcessPriority,
spice2x_ProcessAffinity,
HeapSize,
DisableGSyncDetection,
DisableOverlay,
DisableAudioHooks,
AudioBackend,
AsioDriverId,
AudioDummy,
DelayBy5Seconds,
spice2x_DelayByNSeconds,
LoadStubs,
AdjustOrientation,
spice2x_AutoOrientation,
LogLevel,
EAAutomap,
EANetdump,
@@ -147,9 +164,17 @@ namespace launcher {
DisableDebugHooks,
DisableAvsVfsDriveMountRedirection,
OutputPEB,
spice2x_LightsOverallBrightness,
spice2x_WindowBorder,
spice2x_WindowSize,
spice2x_WindowPosition,
spice2x_WindowAlwaysOnTop,
spice2x_JubeatLegacyTouch,
spice2x_RBTouchScale,
};
}
const std::vector<std::string> &get_categories(bool advanced);
const std::vector<OptionDefinition> &get_option_definitions();
std::unique_ptr<std::vector<Option>> parse_options(int argc, char *argv[]);
std::vector<Option> merge_options(const std::vector<Option> &options, const std::vector<Option> &overrides);
+96
View File
@@ -656,6 +656,102 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
DSEG14Classic-Italic.ttf (OFL)
-------------------------------------------
Copyright (c) 2017, keshikan (http://www.keshikan.net),
with Reserved Font Name "DSEG".
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
Contributions
-------------------------------------------
cardio - Felix - MIT License
+8
View File
@@ -7,6 +7,7 @@
#include "cfg/config.h"
#include "games/io.h"
#include "rawinput/rawinput.h"
#include "games/sdvx/sdvx.h"
#include "util/logging.h"
#include "util/time.h"
#include "util/utils.h"
@@ -299,6 +300,13 @@ uint16_t eamuse_get_keypad_state(size_t unit) {
return 0;
}
// keypad disabled for KFC in BI2X mode, since the game won't handle it correctly
// this check for IO is necessary (as opposed to just doing a check for Valkyrie cab mode)
// because there are hex edits that allow you to use legacy (KFC/BIO2) IO while in Valk mode.
if (avs::game::is_model("KFC") && games::sdvx::BI2X_INITIALIZED) {
return 0;
}
// reset
KEYPAD_STATE[unit] = KEYPAD_STATE_OVERRIDES[unit];
KEYPAD_STATE[unit] |= KEYPAD_STATE_OVERRIDES_BT5[unit];
+85 -27
View File
@@ -15,10 +15,19 @@
#include "util/time.h"
#include "util/utils.h"
#include "avs/game.h"
namespace wintouchemu {
typedef struct {
POINT pos;
bool last_button_pressed;
DWORD touch_event;
} mouse_state_t;
// settings
bool FORCE = false;
bool INJECT_MOUSE_AS_WM_TOUCH = false;
bool LOG_FPS = false;
static inline bool is_emu_enabled() {
@@ -67,6 +76,7 @@ namespace wintouchemu {
std::string WINDOW_TITLE_START = "";
std::optional<std::string> WINDOW_TITLE_END = std::nullopt;
bool INITIALIZED = false;
mouse_state_t mouse_state;
void hook(const char *window_title, HMODULE module) {
@@ -106,7 +116,6 @@ namespace wintouchemu {
// set touch inputs
bool result = false;
bool mouse_used = false;
static bool mouse_state_old = false;
for (UINT input = 0; input < cInputs; input++) {
auto *touch_input = &pInputs[input];
@@ -130,22 +139,34 @@ namespace wintouchemu {
// check touch point
if (touch_event) {
// set touch point
result = true;
touch_input->x = touch_event->x * 100;
touch_input->y = touch_event->y * 100;
auto x = touch_event->x;
auto y = touch_event->y;
auto valid = true;
if (overlay::OVERLAY) {
valid = overlay::OVERLAY->transform_touch_point(&x, &y);
}
touch_input->x = x * 100;
touch_input->y = y * 100;
touch_input->hSource = hTouchInput;
touch_input->dwID = touch_event->id;
touch_input->dwFlags = 0;
switch (touch_event->type) {
case TOUCH_DOWN:
if (valid) {
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
}
break;
case TOUCH_MOVE:
if (valid) {
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
}
break;
case TOUCH_UP:
// don't check valid so that this touch ID can be released
touch_input->dwFlags |= TOUCHEVENTF_UP;
break;
}
@@ -160,39 +181,47 @@ namespace wintouchemu {
// disable further mouse inputs this call
mouse_used = true;
// get mouse state
bool mouse_state = (GetKeyState(VK_LBUTTON) & 0x100) != 0;
// check event needs to be generated
if (mouse_state || mouse_state_old) {
// get current cursor position
POINT cursorPos {};
GetCursorPos(&cursorPos);
// set touch input to mouse
if (mouse_state.touch_event) {
result = true;
touch_input->x = cursorPos.x * 100;
touch_input->y = cursorPos.y * 100;
touch_input->x = mouse_state.pos.x;
touch_input->y = mouse_state.pos.y;
auto valid = true;
if (overlay::OVERLAY) {
valid = overlay::OVERLAY->transform_touch_point(
&touch_input->x, &touch_input->y);
}
// touch inputs require 100x precision per pixel
touch_input->x *= 100;
touch_input->y *= 100;
touch_input->hSource = hTouchInput;
touch_input->dwID = 0;
touch_input->dwFlags = 0;
if (mouse_state && !mouse_state_old) {
switch (mouse_state.touch_event) {
case TOUCHEVENTF_DOWN:
if (valid) {
touch_input->dwFlags |= TOUCHEVENTF_DOWN;
} else if (mouse_state && mouse_state_old) {
}
break;
case TOUCHEVENTF_MOVE:
if (valid) {
touch_input->dwFlags |= TOUCHEVENTF_MOVE;
} else if (!mouse_state && mouse_state_old) {
}
break;
case TOUCHEVENTF_UP:
// don't check valid so that this touch ID can be released
touch_input->dwFlags |= TOUCHEVENTF_UP;
break;
}
touch_input->dwMask = 0;
touch_input->dwTime = 0;
touch_input->dwExtraInfo = 0;
touch_input->cxContact = 0;
touch_input->cyContact = 0;
}
// save old state
mouse_state_old = mouse_state;
// reset it since the event was consumed & propagated as touch
mouse_state.touch_event = 0;
}
} else {
/*
@@ -314,12 +343,18 @@ namespace wintouchemu {
// create touch window - create overlay if not yet existing at this point
touch_create_wnd(hWnd, overlay::ENABLED && !overlay::OVERLAY);
USE_MOUSE = false;
} else if (INJECT_MOUSE_AS_WM_TOUCH) {
log_info(
"wintouchemu",
"using raw mouse cursor API in full screen and injecting them as WM_TOUCH events");
USE_MOUSE = true;
} else {
log_info("wintouchemu", "activating DirectX hooks");
// mouse position based input only
touch_attach_dx_hook();
USE_MOUSE = true;
USE_MOUSE = false;
}
// hooks
@@ -343,9 +378,6 @@ namespace wintouchemu {
// get event count
auto event_count = TOUCH_EVENTS.size();
if (USE_MOUSE) {
event_count++;
}
// for the fake move events
event_count += MAX(0, (int) (TOUCH_POINTS.size() - TOUCH_EVENTS.size()));
@@ -372,5 +404,31 @@ namespace wintouchemu {
}
}
}
// send separate WM_TOUCH event for mouse
// this must be separate from actual touch events because some games will ignore the return
// value from GetTouchInputInfo or fail to read dwFlags for valid events, so it's not OK to
// send empty events when the mouse button is not clicked/released
if (hWnd != nullptr && USE_MOUSE) {
bool button_pressed = ((GetKeyState(VK_LBUTTON) & 0x100) != 0);
// figure out what kind of touch event to simulate
if (button_pressed && !mouse_state.last_button_pressed) {
mouse_state.touch_event = TOUCHEVENTF_DOWN;
} else if (button_pressed && mouse_state.last_button_pressed) {
mouse_state.touch_event = TOUCHEVENTF_MOVE;
} else if (!button_pressed && mouse_state.last_button_pressed) {
mouse_state.touch_event = TOUCHEVENTF_UP;
}
mouse_state.last_button_pressed = button_pressed;
if (mouse_state.touch_event) {
GetCursorPos(&mouse_state.pos);
// send fake event to make the game update it's touch inputs
auto wndProc = (WNDPROC) GetWindowLongPtr(hWnd, GWLP_WNDPROC);
wndProc(hWnd, WM_TOUCH, MAKEWORD(1, 0), (LPARAM) GetTouchInputInfoHook);
}
}
}
}
+1
View File
@@ -6,6 +6,7 @@ namespace wintouchemu {
// settings
extern bool FORCE;
extern bool INJECT_MOUSE_AS_WM_TOUCH;
extern bool LOG_FPS;
void hook(const char *window_title, HMODULE module = nullptr);
+5 -2
View File
@@ -313,7 +313,10 @@ void ImGui_ImplSpice_NewFrame() {
// trigger
io.KeysDown[vKey] |= state;
if (!KeysDownOld[vKey] && state) {
// generate character input, but only if WM_CHAR didn't take over the
// functionality
if (!overlay::USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT && !KeysDownOld[vKey] && state) {
UCHAR buf[2];
auto ret = ToAscii(
static_cast<UINT>(vKey),
@@ -323,7 +326,7 @@ void ImGui_ImplSpice_NewFrame() {
0);
if (ret > 0) {
for (int i = 0; i < ret; i++) {
overlay::OVERLAY->input_char(buf[i], true);
overlay::OVERLAY->input_char(buf[i]);
}
}
}
+160 -43
View File
@@ -3,10 +3,13 @@
#include "avs/game.h"
#include "cfg/configurator.h"
#include "games/io.h"
#include "games/iidx/iidx.h"
#include "hooks/graphics/graphics.h"
#include "misc/eamuse.h"
#include "touch/touch.h"
#include "util/logging.h"
#include "util/resutils.h"
#include "build/resource.h"
#include "imgui/impl_dx9.h"
#include "imgui/impl_spice.h"
@@ -21,7 +24,13 @@
#include "windows/config.h"
#include "windows/control.h"
#include "windows/fps.h"
#include "windows/generic_sub.h"
#include "windows/iidx_seg.h"
#include "windows/iidx_sub.h"
#include "windows/iopanel.h"
#include "windows/iopanel_ddr.h"
#include "windows/iopanel_gfdm.h"
#include "windows/iopanel_iidx.h"
#include "windows/sdvx_sub.h"
#include "windows/keypad.h"
#include "windows/kfcontrol.h"
@@ -29,14 +38,25 @@
#include "windows/patch_manager.h"
#include "windows/vr.h"
static inline ImVec4 operator*(const ImVec4& lhs, const ImVec4& rhs) \
{ return ImVec4(lhs.x * rhs.x, lhs.y * rhs.y, lhs.z * rhs.z, lhs.w * rhs.w); }
namespace overlay {
// settings
bool ENABLED = true;
bool AUTO_SHOW_FPS = false;
bool AUTO_SHOW_SUBSCREEN = false;
bool AUTO_SHOW_IOPANEL = false;
bool AUTO_SHOW_KEYPAD_P1 = false;
bool AUTO_SHOW_KEYPAD_P2 = false;
bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT = false;
// global
std::mutex OVERLAY_MUTEX;
std::unique_ptr<overlay::SpiceOverlay> OVERLAY = nullptr;
ImFont* DSEG_FONT = nullptr;
}
static void *ImGui_Alloc(size_t sz, void *user_data) {
@@ -146,37 +166,71 @@ void overlay::SpiceOverlay::init() {
style.WindowRounding = 0;
}
// red theme
/*
// red theme based on:
// https://github.com/ocornut/imgui/issues/707#issuecomment-760220280
// r, g, b, a
ImVec4* colors = ImGui::GetStyle().Colors;
colors[ImGuiCol_Border] = ImVec4(0.50f, 0.43f, 0.43f, 0.50f);
colors[ImGuiCol_FrameBg] = ImVec4(0.48f, 0.16f, 0.16f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.98f, 0.26f, 0.26f, 0.40f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.98f, 0.26f, 0.26f, 0.67f);
// colors[ImGuiCol_Text] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f);
// colors[ImGuiCol_TextDisabled] = ImVec4(0.35f, 0.35f, 0.35f, 1.00f);
// colors[ImGuiCol_WindowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.94f);
// colors[ImGuiCol_ChildBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
// colors[ImGuiCol_PopupBg] = ImVec4(0.08f, 0.08f, 0.08f, 0.94f);
colors[ImGuiCol_Border] = ImVec4(0.00f, 0.00f, 0.00f, 0.50f);
colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_FrameBg] = ImVec4(0.37f, 0.14f, 0.00f, 0.54f);
colors[ImGuiCol_FrameBgHovered] = ImVec4(0.37f, 0.14f, 0.14f, 0.67f);
colors[ImGuiCol_FrameBgActive] = ImVec4(0.39f, 0.20f, 0.20f, 0.67f);
colors[ImGuiCol_TitleBg] = ImVec4(0.04f, 0.04f, 0.04f, 1.00f);
colors[ImGuiCol_TitleBgActive] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.98f, 0.26f, 0.26f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(0.88f, 0.24f, 0.24f, 1.00f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.98f, 0.26f, 0.26f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(0.98f, 0.26f, 0.26f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.98f, 0.26f, 0.26f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.98f, 0.06f, 0.06f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.98f, 0.26f, 0.26f, 0.31f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.98f, 0.26f, 0.26f, 0.80f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.98f, 0.26f, 0.26f, 1.00f);
colors[ImGuiCol_Separator] = ImVec4(0.50f, 0.43f, 0.43f, 0.50f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.75f, 0.10f, 0.10f, 0.78f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.75f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(0.98f, 0.26f, 0.26f, 0.25f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.98f, 0.26f, 0.26f, 0.67f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(0.98f, 0.26f, 0.26f, 0.95f);
colors[ImGuiCol_Tab] = ImVec4(0.58f, 0.18f, 0.18f, 0.86f);
colors[ImGuiCol_TabHovered] = ImVec4(0.98f, 0.26f, 0.26f, 0.80f);
colors[ImGuiCol_TabActive] = ImVec4(0.68f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_TabUnfocused] = ImVec4(0.15f, 0.07f, 0.07f, 0.97f);
colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.42f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.98f, 0.26f, 0.26f, 0.35f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.98f, 0.26f, 0.26f, 1.00f);
*/
colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.48f, 0.16f, 0.16f, 1.00f);
colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f);
colors[ImGuiCol_ScrollbarBg] = ImVec4(0.02f, 0.02f, 0.02f, 0.53f);
colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.31f, 0.31f, 0.31f, 1.00f);
colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f);
colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.51f, 0.51f, 0.51f, 1.00f);
colors[ImGuiCol_CheckMark] = ImVec4(0.56f, 0.10f, 0.10f, 1.00f);
colors[ImGuiCol_SliderGrab] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
colors[ImGuiCol_SliderGrabActive] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
colors[ImGuiCol_Button] = ImVec4(1.00f, 0.19f, 0.19f, 0.40f);
colors[ImGuiCol_ButtonHovered] = ImVec4(0.80f, 0.17f, 0.00f, 1.00f);
colors[ImGuiCol_ButtonActive] = ImVec4(0.89f, 0.00f, 0.19f, 1.00f);
colors[ImGuiCol_Header] = ImVec4(0.33f, 0.35f, 0.36f, 0.53f);
colors[ImGuiCol_HeaderHovered] = ImVec4(0.76f, 0.28f, 0.44f, 0.67f);
colors[ImGuiCol_HeaderActive] = ImVec4(0.47f, 0.47f, 0.47f, 0.67f);
colors[ImGuiCol_Separator] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_SeparatorHovered] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_SeparatorActive] = ImVec4(0.32f, 0.32f, 0.32f, 1.00f);
colors[ImGuiCol_ResizeGrip] = ImVec4(1.00f, 1.00f, 1.00f, 0.85f);
colors[ImGuiCol_ResizeGripHovered] = ImVec4(1.00f, 1.00f, 1.00f, 0.60f);
colors[ImGuiCol_ResizeGripActive] = ImVec4(1.00f, 1.00f, 1.00f, 0.90f);
colors[ImGuiCol_Tab] = colors[ImGuiCol_Button];
colors[ImGuiCol_TabHovered] = colors[ImGuiCol_ButtonHovered];
colors[ImGuiCol_TabActive] = colors[ImGuiCol_ButtonActive];
colors[ImGuiCol_TabUnfocused] = colors[ImGuiCol_Tab] * ImVec4(1.0f, 1.0f, 1.0f, 0.6f);
colors[ImGuiCol_TabUnfocusedActive] = colors[ImGuiCol_TabActive] * ImVec4(1.0f, 1.0f, 1.0f, 0.6f);
colors[ImGuiCol_DockingPreview] = ImVec4(0.47f, 0.47f, 0.47f, 0.47f);
colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f);
colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f);
colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f);
colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f);
colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f);
colors[ImGuiCol_TableHeaderBg] = ImVec4(0.19f, 0.19f, 0.20f, 1.00f);
colors[ImGuiCol_TableBorderStrong] = ImVec4(0.31f, 0.31f, 0.35f, 1.00f);
colors[ImGuiCol_TableBorderLight] = ImVec4(0.23f, 0.23f, 0.25f, 1.00f);
colors[ImGuiCol_TableRowBg] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f);
colors[ImGuiCol_TableRowBgAlt] = ImVec4(1.00f, 1.00f, 1.00f, 0.07f);
colors[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.59f, 0.98f, 0.35f);
colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f);
// configure IO
auto &io = ImGui::GetIO();
@@ -219,6 +273,16 @@ void overlay::SpiceOverlay::init() {
io.Fonts->AddFontFromFileTTF(R"(C:\Windows\Fonts\arial.ttf)",
13.0f, &config, io.Fonts->GetGlyphRangesVietnamese());
// add special font
if (avs::game::is_model("LDJ")) {
DWORD size;
ImFontConfig config {};
config.FontDataOwnedByAtlas = false;
auto buffer = resutil::load_file(IDR_DSEGFONT, &size);
DSEG_FONT = io.Fonts->AddFontFromMemoryTTF((void *)buffer, size,
overlay::windows::IIDX_SEGMENT_FONT_SIZE);
}
// init implementation
ImGui_ImplSpice_Init(this->hWnd);
switch (this->renderer) {
@@ -246,27 +310,88 @@ void overlay::SpiceOverlay::init() {
ImGui::Begin("Temp");
ImGui::End();
bool set_overlay_active = false;
// referenced windows
this->window_add(window_fps = new overlay::windows::FPS(this));
if (!cfg::CONFIGURATOR_STANDALONE && AUTO_SHOW_FPS) {
window_fps->set_active(true);
set_overlay_active = true;
}
// add default windows
this->window_add(new overlay::windows::Config(this));
this->window_add(new overlay::windows::Control(this));
this->window_add(new overlay::windows::Log(this));
this->window_add(new overlay::windows::CardManager(this));
if (!cfg::CONFIGURATOR_STANDALONE) {
this->window_add(new overlay::windows::ScreenResize(this));
}
this->window_add(new overlay::windows::PatchManager(this));
this->window_add(new overlay::windows::Keypad(this, 0));
this->window_add(new overlay::windows::KFControl(this));
this->window_add(new overlay::windows::VRWindow(this));
{
const auto keypad_p1 = new overlay::windows::Keypad(this, 0);
this->window_add(keypad_p1);
if (!cfg::CONFIGURATOR_STANDALONE && AUTO_SHOW_KEYPAD_P1) {
keypad_p1->set_active(true);
set_overlay_active = true;
}
}
if (eamuse_get_game_keypads() > 1) {
this->window_add(new overlay::windows::Keypad(this, 1));
const auto keypad_p2 = new overlay::windows::Keypad(this, 1);
this->window_add(keypad_p2);
if (!cfg::CONFIGURATOR_STANDALONE && AUTO_SHOW_KEYPAD_P2) {
keypad_p2->set_active(true);
set_overlay_active = true;
}
}
// IO panel needs to know what game is running
if (!cfg::CONFIGURATOR_STANDALONE) {
overlay::Window *iopanel = nullptr;
if (avs::game::is_model("LDJ")) {
this->window_add(new overlay::windows::IIDXSubScreen(this));
iopanel = new overlay::windows::IIDXIOPanel(this);
} else if (avs::game::is_model("MDX")) {
iopanel = new overlay::windows::DDRIOPanel(this);
} else if (avs::game::is_model({"J32", "J33", "K32", "K33", "L32", "L33", "M32"})) {
iopanel = new overlay::windows::GitadoraIOPanel(this);
} else {
iopanel = new overlay::windows::IOPanel(this);
}
if (avs::game::is_model("KFC")) {
this->window_add(new overlay::windows::SDVXSubScreen(this));
if (iopanel) {
this->window_add(iopanel);
if (AUTO_SHOW_IOPANEL) {
iopanel->set_active(true);
set_overlay_active = true;
}
}
}
// subscreens need DirectX, so don't try to initialize them in standalone
if (!cfg::CONFIGURATOR_STANDALONE) {
overlay::Window *subscreen = nullptr;
if (avs::game::is_model("LDJ")) {
if (games::iidx::TDJ_MODE) {
subscreen = new overlay::windows::IIDXSubScreen(this);
} else {
subscreen = new overlay::windows::IIDXSegmentDisplay(this);
}
} else if (avs::game::is_model("KFC")) {
subscreen = new overlay::windows::SDVXSubScreen(this);
}
if (subscreen) {
this->window_add(subscreen);
if (AUTO_SHOW_SUBSCREEN) {
subscreen->set_active(true);
set_overlay_active = true;
}
}
}
if (set_overlay_active) {
this->set_active(true);
}
}
@@ -533,15 +658,7 @@ void overlay::SpiceOverlay::reset_recreate() {
ImGui_ImplDX9_CreateDeviceObjects();
}
void overlay::SpiceOverlay::input_char(unsigned int c, bool rawinput) {
// disable rawinput characters once we got one from somewhere else (such as a WndProc)
if (!rawinput) {
this->rawinput_char = false;
} else if (!this->rawinput_char) {
return;
}
void overlay::SpiceOverlay::input_char(unsigned int c) {
// add character to ImGui
ImGui::GetIO().AddInputCharacter(c);
}
+27 -2
View File
@@ -2,6 +2,7 @@
#include <memory>
#include <mutex>
#include <functional>
#include <vector>
#include <windows.h>
#include <d3d9.h>
@@ -18,6 +19,12 @@ namespace overlay {
// settings
extern bool ENABLED;
extern bool AUTO_SHOW_FPS;
extern bool AUTO_SHOW_SUBSCREEN;
extern bool AUTO_SHOW_IOPANEL;
extern bool AUTO_SHOW_KEYPAD_P1;
extern bool AUTO_SHOW_KEYPAD_P2;
extern bool USE_WM_CHAR_FOR_IMGUI_CHAR_INPUT;
class SpiceOverlay {
public:
@@ -43,7 +50,7 @@ namespace overlay {
static bool update_cursor();
static void reset_invalidate();
static void reset_recreate();
void input_char(unsigned int c, bool rawinput = false);
void input_char(unsigned int c);
uint32_t *sw_get_pixel_data(int *width, int *height);
@@ -60,6 +67,22 @@ namespace overlay {
return this->device;
}
bool can_transform_touch_input() {
return (this->subscreen_mouse_handler != nullptr);
}
bool transform_touch_point(LONG *x, LONG *y) {
if (this->get_active() && this->subscreen_mouse_handler) {
return this->subscreen_mouse_handler(x, y);
} else {
return true;
}
}
void set_subscreen_mouse_handler(const std::function<bool(LONG *, LONG *)> &f) {
this->subscreen_mouse_handler = f;
}
// renderer
OverlayRenderer renderer;
float total_elapsed = 0.f;
@@ -80,9 +103,10 @@ namespace overlay {
std::vector<std::unique_ptr<Window>> windows;
Window *window_fps = nullptr;
std::function<bool(LONG *, LONG *)> subscreen_mouse_handler = nullptr;
bool active = false;
bool toggle_down = false;
bool rawinput_char = true;
bool hotkey_toggle = false;
bool hotkey_toggle_last = false;
@@ -92,6 +116,7 @@ namespace overlay {
// global
extern std::mutex OVERLAY_MUTEX;
extern std::unique_ptr<overlay::SpiceOverlay> OVERLAY;
extern ImFont* DSEG_FONT;
// synchronized helpers
void create_d3d9(HWND hWnd, IDirect3D9 *d3d, IDirect3DDevice9 *device);
+17 -3
View File
@@ -65,9 +65,9 @@ void overlay::Window::build() {
if (!cfg::CONFIGURATOR_STANDALONE && (size_max.x < 0 || size_max.y < 0)) {
auto &display_size = ImGui::GetIO().DisplaySize;
ImVec2 size_max_auto(display_size.x - 100, display_size.y - 100);
ImGui::SetNextWindowSizeConstraints(size_min, size_max_auto);
ImGui::SetNextWindowSizeConstraints(size_min, size_max_auto, resize_callback);
} else {
ImGui::SetNextWindowSizeConstraints(size_min, size_max);
ImGui::SetNextWindowSizeConstraints(size_min, size_max, resize_callback);
}
// background alpha
@@ -75,6 +75,12 @@ void overlay::Window::build() {
ImGui::SetNextWindowBgAlpha(this->bg_alpha);
}
if (this->remove_window_padding) {
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
}
// create window
if (ImGui::Begin(
(this->title + "###" + to_string(this)).c_str(),
@@ -82,7 +88,8 @@ void overlay::Window::build() {
this->flags)) {
// window attributes
ImGui::SetWindowPos(this->initial_pos(), ImGuiCond_Once);
this->calculate_initial_window();
ImGui::SetWindowPos(this->init_pos, ImGuiCond_Once);
ImGui::SetWindowSize(this->init_size, ImGuiCond_Once);
// add content
@@ -96,6 +103,13 @@ void overlay::Window::build() {
// end window
ImGui::End();
}
if (this->remove_window_padding) {
ImGui::PopStyleVar();
ImGui::PopStyleVar();
ImGui::PopStyleVar();
}
} else {
// add raw content
+7 -4
View File
@@ -10,9 +10,11 @@ namespace overlay {
class Window {
public:
virtual ~Window();
virtual const ImVec2 initial_pos() {
return this->init_pos;
}
// an opportunity to calculate initial window position and size within the ImGui
// window context, since it may not be possible in the Window constructor
virtual void calculate_initial_window() {}
virtual void build_content() = 0;
virtual void update();
virtual void after_render() {};
@@ -21,7 +23,6 @@ namespace overlay {
void toggle_active();
void set_active(bool active);
bool get_active();
protected:
// state
@@ -30,7 +31,9 @@ namespace overlay {
std::vector<Window*> children;
// settings
bool remove_window_padding = false;
bool draws_window = true;
ImGuiSizeCallback resize_callback = nullptr;
std::string title = "Title";
ImGuiWindowFlags flags = 0;
size_t toggle_button = ~0u;
+1 -1
View File
@@ -17,7 +17,7 @@ namespace overlay::windows {
// configure editor defaults
this->editor = new MemoryEditor();
this->editor->OptShowDataPreview = true;
this->editor->PreviewDataType = MemoryEditor::DataType::DataType_U16;
this->editor->PreviewDataType = ImGuiDataType_U16;
}
ACIOStatusBuffers::~ACIOStatusBuffers() {
+147 -83
View File
@@ -4,6 +4,7 @@
#include <thread>
#include <windows.h>
#include <shellapi.h>
#include <commdlg.h>
#include "build/defs.h"
@@ -17,6 +18,7 @@
#include "avs/game.h"
#include "launcher/launcher.h"
#include "launcher/shutdown.h"
#include "launcher/options.h"
#include "misc/eamuse.h"
#include "overlay/imgui/extensions.h"
#include "rawinput/piuio.h"
@@ -34,6 +36,8 @@
namespace overlay::windows {
const auto PROJECT_URL = "https://spice2x.github.io";
Config::Config(overlay::SpiceOverlay *overlay) : Window(overlay) {
this->title = "Configuration";
this->toggle_button = games::OverlayButtons::ToggleConfig;
@@ -238,6 +242,29 @@ namespace overlay::windows {
// keypad buttons
ImGui::TextUnformatted("");
if (this->games_selected_name == "Sound Voltex") {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Valkyrie Cabinet I/O will ignore the keypad!");
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextWrapped(
"Use Toggle Sub Screen button to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN.");
ImGui::TextUnformatted("");
} else if (this->games_selected_name == "Beatmania IIDX") {
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextColored(
ImVec4(1, 0.5f, 0.5f, 1.f),
"WARNING: Lightning Model (TDJ) I/O will ignore the keypad!");
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextWrapped(
"Use Toggle Sub Screen button to show the overlay and use your mouse, "
"connect using SpiceCompanion app, or connect a touch screen to enter "
"the PIN.");
ImGui::TextUnformatted("");
}
auto keypad_buttons = games::get_buttons_keypads(this->games_selected_name);
auto keypad_count = eamuse_get_game_keypads_name();
if (keypad_count == 1) {
@@ -336,39 +363,44 @@ namespace overlay::windows {
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Options")) {
// options list
ImGui::BeginChild("Options", ImVec2(
0, ImGui::GetWindowContentRegionMax().y - page_offset), false);
// get options and categories
auto options = games::get_options(this->games_selected_name);
std::vector<const char *> categories;
categories.push_back("Everything");
if (options) {
for (auto &option : *options) {
bool found = false;
for (auto &category : categories) {
if (strcmp(option.get_definition().category.c_str(), category) == 0) {
found = true;
break;
}
}
if (!found) {
categories.push_back(option.get_definition().category.c_str());
}
}
}
// get current category
std::string category = "";
if (this->options_category != -1 && this->options_category < (int) categories.size()) {
category = categories[this->options_category];
if (category == "Everything") {
category = "";
}
}
// options list
for (auto category : launcher::get_categories(false)) {
this->build_options(options, category);
}
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Advanced Options");
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + 8);
ImGui::TextWrapped(
"Rest of the options have moved to Advanced tab.");
ImGui::TextUnformatted("");
ImGui::TextUnformatted("");
ImGui::EndChild();
// hidden options checkbox
ImGui::Checkbox("Show Hidden Options", &this->options_show_hidden);
if (!cfg::CONFIGURATOR_STANDALONE && this->options_dirty) {
ImGui::SameLine();
if (ImGui::Button("Restart Game")) {
launcher::restart();
}
ImGui::SameLine();
ImGui::HelpMarker("You need to restart the game to apply the changed settings.");
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Advanced")) {
// options list
ImGui::BeginChild("AdvancedOptions", ImVec2(
0, ImGui::GetWindowContentRegionMax().y - page_offset), false);
auto options = games::get_options(this->games_selected_name);
for (auto category : launcher::get_categories(true)) {
this->build_options(options, category);
}
ImGui::EndChild();
// hidden options checkbox
@@ -402,12 +434,6 @@ namespace overlay::windows {
ImGui::EndPopup();
}
// category selection
ImGui::SameLine();
ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.3f);
ImGui::Combo("Category", &this->options_category, categories.data(), categories.size());
ImGui::PopItemWidth();
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("About")) {
@@ -488,10 +514,10 @@ namespace overlay::windows {
style_color = true;
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.7f, 0.7f, 0.7f, 1.f));
}
vertical_align_text_column();
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", button_name.c_str());
ImGui::NextColumn();
vertical_align_text_column();
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", button_display.empty() ? "None" : button_display.c_str());
ImGui::NextColumn();
if (style_color) {
@@ -622,8 +648,10 @@ namespace overlay::windows {
case rawinput::HID: {
auto hid = device->hidInfo;
// ignore touchscreen button inputs
if (!rawinput::touch::is_touchscreen(device)) {
// ignore touchscreen and digitizer button inputs
// digitizer has funky stuff like "Touch Valid" "Data Valid" always held high
if (!rawinput::touch::is_touchscreen(device) &&
hid->caps.UsagePage != 0x0D) {
// button caps
auto button_states_list = &hid->button_states;
@@ -1179,7 +1207,7 @@ namespace overlay::windows {
}
ImGui::Text("%s", analog_name.c_str());
ImGui::NextColumn();
vertical_align_text_column();
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", analog_display.empty() ? "None" : analog_display.c_str());
ImGui::NextColumn();
if (analog_display.empty()) {
@@ -1502,7 +1530,7 @@ namespace overlay::windows {
ImGui::SameLine();
ImGui::Text("%s", light_name.c_str());
ImGui::NextColumn();
vertical_align_text_column();
ImGui::AlignTextToFramePadding();
ImGui::Text("%s", light_display.empty() ? "None" : light_display.c_str());
ImGui::NextColumn();
if (light_display.empty()) {
@@ -1807,7 +1835,7 @@ namespace overlay::windows {
if (!cfg::CONFIGURATOR_STANDALONE && !this->keypads_card_select_browser[player].IsOpened()) {
this->keypads_card_select_browser[player].SetTitle("Card Select");
this->keypads_card_select_browser[player].SetTypeFilters({".txt", "*"});
this->keypads_card_select_browser[player].flags_ |= ImGuiFileBrowserFlags_EnterNewFilename;
// this->keypads_card_select_browser[player].flags_ |= ImGuiFileBrowserFlags_EnterNewFilename;
this->keypads_card_select_browser[player].Open();
}
}
@@ -1903,31 +1931,25 @@ namespace overlay::windows {
}
void Config::build_options(std::vector<Option> *options, const std::string &category) {
int options_count;
ImGui::Columns(3, "OptionsColumns", true);
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Title"); ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), category.c_str());
ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Parameter");
ImGui::SameLine();
ImGui::HelpMarker("These are the command-line parameters you can use in your .bat file to set the options.\n"
ImGui::HelpMarker(
"These are the command-line parameters you can use in your .bat file to set the options.\n"
"Example: spice.exe -w -ea\n"
" spice64.exe -api 1337 -apipass changeme");
ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Setting"); ImGui::NextColumn();
ImGui::TextColored(ImVec4(1.f, 0.7f, 0, 1), "Setting");
ImGui::NextColumn();
ImGui::Separator();
// check if empty
if (!options || options->empty()) {
ImGui::TextUnformatted("-");
ImGui::NextColumn();
ImGui::TextUnformatted("-");
ImGui::NextColumn();
ImGui::TextUnformatted("-");
ImGui::NextColumn();
}
// iterate options
if (options) {
options_count = 0;
for (auto &option : *options) {
// get option definition
auto &definition = option.get_definition();
@@ -1952,9 +1974,11 @@ namespace overlay::windows {
}
}
options_count += 1;
// list entry
ImGui::PushID(&option);
vertical_align_text_column();
ImGui::AlignTextToFramePadding();
ImGui::HelpMarker(definition.desc.c_str());
ImGui::SameLine();
if (option.is_active()) {
@@ -1972,7 +1996,7 @@ namespace overlay::windows {
ImGui::Text("%s", definition.title.c_str());
}
ImGui::NextColumn();
vertical_align_text_column();
ImGui::AlignTextToFramePadding();
ImGui::Text("-%s", definition.name.c_str());
ImGui::NextColumn();
if (option.disabled) {
@@ -1997,7 +2021,7 @@ namespace overlay::windows {
if ('0' <= data->EventChar && data->EventChar <= '9') {
return 0;
}
return 1;
return 1; // discard
};
const char *hint = definition.setting_name.empty() ? "Enter number..."
@@ -2012,6 +2036,37 @@ namespace overlay::windows {
}
break;
}
case OptionType::Hex: {
char buffer[512];
strncpy(buffer, option.value.c_str(), sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';
auto digits_filter = [](ImGuiInputTextCallbackData* data) {
if ('0' <= data->EventChar && data->EventChar <= '9') {
return 0;
}
if ('a' <= data->EventChar && data->EventChar <= 'f') {
return 0;
}
if ('A' <= data->EventChar && data->EventChar <= 'F') {
return 0;
}
if (data->EventChar == 'x' || data->EventChar == 'X') {
return 0;
}
return 1; // discard
};
const char *hint = definition.setting_name.empty() ? "Enter hex..."
: definition.setting_name.c_str();
if (ImGui::InputTextWithHint("", hint,
buffer, sizeof(buffer) - 1,
ImGuiInputTextFlags_CallbackCharFilter, digits_filter)) {
this->options_dirty = true;
option.value = buffer;
::Config::getInstance().updateBinding(games_list[games_selected], option);
}
break;
}
case OptionType::Text: {
char buffer[512];
strncpy(buffer, option.value.c_str(), sizeof(buffer) - 1);
@@ -2082,8 +2137,8 @@ namespace overlay::windows {
// disabled help
if (option.disabled) {
ImGui::SameLine();
ImGui::HelpMarker("This option can not be edited because it was overriden by command-line "
"options.\n"
ImGui::HelpMarker(
"This option can not be edited because it was overriden by command-line options.\n"
"Run spicecfg.exe to configure the options and then run spice(64).exe directly.");
}
@@ -2091,30 +2146,43 @@ namespace overlay::windows {
ImGui::PopID();
ImGui::NextColumn();
}
// check if empty
if (options_count == 0) {
ImGui::TextUnformatted("-");
ImGui::NextColumn();
ImGui::TextUnformatted("-");
ImGui::NextColumn();
ImGui::TextUnformatted("-");
ImGui::NextColumn();
}
ImGui::Columns(1);
ImGui::TextUnformatted("");
}
void Config::build_about() {
auto time = get_system_milliseconds();
auto value = ((float) cos(time * 0.0005) + 1.f) * 0.5f;
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.8f - value * 0.3f, 1.f, 1.f, 1.f));
ImGui::TextUnformatted(std::string(
"SpiceTools\r\n"
"spice2x (a fork of SpiceTools)\r\n"
"=========================\r\n" +
to_string(VERSION_STRING) + "\r\n\r\n" + to_string(
resutil::load_file_string_crlf(IDR_README)) +
to_string(VERSION_STRING)).c_str());
ImGui::TextUnformatted("");
if (ImGui::Button(PROJECT_URL)) {
launch_url();
}
ImGui::TextUnformatted("");
ImGui::TextUnformatted(std::string(
resutil::load_file_string_crlf(IDR_README) +
"\r\n" +
"Changelog (Highlights):\r\n" +
resutil::load_file_string_crlf(IDR_CHANGELOG)).c_str());
ImGui::PopStyleColor();
}
void Config::build_licenses() {
auto time = get_system_milliseconds();
auto value = ((float) cos(time * 0.0005) + 1.f) * 0.5f;
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.f, 1.f, 0.9f - value * 0.3f, 1.f));
ImGui::TextUnformatted(resutil::load_file_string_crlf(IDR_LICENSES).c_str());
ImGui::PopStyleColor();
}
void Config::build_launcher() {
@@ -2156,15 +2224,11 @@ namespace overlay::windows {
"at the same time using pages.");
}
void Config::vertical_align_text_column() {
/*
* see https://github.com/ocornut/imgui/issues/1284
* height of most widgets (like Button) is GetFontSize() + GetStyle().FramePadding.y * 2
* height of unformatted text is GetFontSize()
* by default, rows are top-aligned
* so we add FramePadding.y to the top of unformatted text for vertical centering.
*/
auto y = ImGui::GetCursorPosY() + ImGui::GetStyle().FramePadding.y;
ImGui::SetCursorPosY(y);
void Config::launch_url() {
// doing this on a separate thread to avoid polluting ImGui context
std::thread t([] {
ShellExecuteA(NULL, "open", PROJECT_URL, NULL, NULL, SW_SHOWNORMAL);
});
t.join();
}
}
+1 -1
View File
@@ -69,8 +69,8 @@ namespace overlay::windows {
void build_about();
void build_licenses();
void build_launcher();
void launch_url();
static void build_page_selector(int *page);
static void vertical_align_text_column();
};
}
+30 -7
View File
@@ -1,22 +1,26 @@
#include <iomanip>
#include <sstream>
#include "fps.h"
namespace overlay::windows {
FPS::FPS(SpiceOverlay *overlay) : Window(overlay) {
this->title = "FPS & Frame Time";
this->title = "Stats";
this->flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoCollapse
| ImGuiWindowFlags_AlwaysAutoResize
| ImGuiWindowFlags_NoDecoration
| ImGuiWindowFlags_NoFocusOnAppearing
| ImGuiWindowFlags_NoNavFocus
| ImGuiWindowFlags_NoNavInputs;
| ImGuiWindowFlags_NoNavInputs
| ImGuiWindowFlags_NoDocking;
this->bg_alpha = 0.4f;
this->start_time = std::chrono::system_clock::now();
}
const ImVec2 FPS::initial_pos() {
return ImVec2(ImGui::GetIO().DisplaySize.x - 100, 10);
void FPS::calculate_initial_window() {
// width is 114x82 px with window decoration, 98x47 for the content
this->init_pos = ImVec2(ImGui::GetIO().DisplaySize.x - 120, 8);
}
void FPS::build_content() {
@@ -24,6 +28,25 @@ namespace overlay::windows {
// frame timers
ImGuiIO &io = ImGui::GetIO();
ImGui::Text("FPS: %.1f", io.Framerate);
ImGui::Text("FT: %.2fms", 1000 / io.Framerate);
// ImGui::Text("FT: %.2fms", 1000 / io.Framerate);
auto now = std::chrono::system_clock::now();
// current time
{
auto now_t = std::chrono::system_clock::to_time_t(now);
static CHAR buf[48];
std::strftime(buf, sizeof(buf), "Time: %H:%M:%S", std::localtime(&now_t));
ImGui::Text(buf);
}
// elapsed time
{
auto d = now - this->start_time;
const auto h = std::chrono::duration_cast<std::chrono::hours>(d);
const auto m = std::chrono::duration_cast<std::chrono::minutes>(d - h);
const auto s = std::chrono::duration_cast<std::chrono::seconds>(d - h - m);
ImGui::Text("Up: %02d:%02d:%02d", (int)h.count(), (int)m.count(), (int)s.count());
}
}
}
+5 -1
View File
@@ -1,15 +1,19 @@
#pragma once
#include <chrono>
#include "overlay/window.h"
namespace overlay::windows {
class FPS : public Window {
private:
std::chrono::system_clock::time_point start_time;
public:
FPS(SpiceOverlay *overlay);
const ImVec2 initial_pos() override;
void calculate_initial_window() override;
void build_content() override;
};
}
+183
View File
@@ -0,0 +1,183 @@
#undef CINTERFACE
#include "generic_sub.h"
#include <fmt/format.h>
#include "games/io.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "hooks/graphics/backends/d3d9/d3d9_device.h"
#include "util/logging.h"
#include "util/utils.h"
// #define OVERLAYDBG 1
namespace overlay::windows {
const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
const ImVec4 WHITE(1.f, 1.f, 1.f, 1.f);
GenericSubScreen::GenericSubScreen(SpiceOverlay *overlay) : Window(overlay), device(overlay->get_device()) {
this->remove_window_padding = true;
this->flags = ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoBackground |
ImGuiWindowFlags_NoDocking;
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
this->size_min = ImVec2(240, 135 + ImGui::GetFrameHeight());
this->init_size = size_min;
this->resize_callback = keep_16_by_9;
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
this->texture_width = 0;
this->texture_height = 0;
overlay->set_subscreen_mouse_handler([this](LONG *x, LONG *y) -> bool {
// convert to normalized form (relative window coordinates 0.f-1.f)
ImVec2 xy(*x, *y);
xy.x = (xy.x - overlay_content_top_left.x) / overlay_content_size.x;
xy.y = (xy.y - overlay_content_top_left.y) / overlay_content_size.y;
// x/y can be outside of window
if (xy.x < 0.f || 1.f < xy.x || xy.y < 0.f || 1.f < xy.y) {
return false;
}
// call into child
this->touch_transform(xy, x, y);
return true;
});
}
void GenericSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {}
void GenericSubScreen::build_content() {
this->draw_texture();
#if OVERLAYDBG
if (this->status_message.has_value()) {
log_warning("sub::overlay", "{}", this->status_message.value().c_str());
}
if (this->status_message.has_value()) {
ImGui::TextColored(YELLOW, "%s", this->status_message.value().c_str());
} else if (this->texture) {
ImGui::TextColored(WHITE, "Successfully acquired surface texture");
} else {
ImGui::TextColored(YELLOW, "Failed to acquire surface texture");
}
#endif
}
bool GenericSubScreen::build_texture(IDirect3DSurface9 *surface, UINT width, UINT height) {
HRESULT hr;
D3DSURFACE_DESC desc {};
hr = surface->GetDesc(&desc);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to get surface descriptor, hr={}", FMT_HRESULT(hr));
return false;
}
hr = this->device->CreateTexture(width, height, 0, desc.Usage, desc.Format,
desc.Pool, &this->texture, nullptr);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to create render target, hr={}", FMT_HRESULT(hr));
return false;
}
this->texture_width = width;
this->texture_height = height;
return true;
}
void GenericSubScreen::draw_texture() {
HRESULT hr;
auto surface = graphics_d3d9_ldj_get_sub_screen();
if (surface == nullptr) {
return;
}
if (this->draws_window) {
// calculate the **content** location and size.
// GetContentRegionAvail returns the correct dimension, up to (and including) the resize handle
// don't be tempted to change it to some other ImGui routine that accomplishes similar things!
overlay_content_top_left = ImGui::GetCursorScreenPos();
overlay_content_size = ImGui::GetContentRegionAvail();
} else {
// no window, full screen
overlay_content_top_left = ImVec2(0, 0);
overlay_content_size = ImGui::GetIO().DisplaySize;
}
if (this->draws_window &&
this->texture &&
((UINT)overlay_content_size.x != this->texture_width)) {
#if OVERLAYDBG
log_info("sub::overlay", "resize {} != {} ", overlay_content_size.x, this->texture_width);
#endif
// hack needed for SDVX; resizing the texture results in darker image, so allocate texture
// again with the new size when the window is resized
this->texture->Release();
this->texture = nullptr;
this->texture_width = 0;
this->texture_height = 0;
}
if (this->texture == nullptr) {
if (!this->build_texture(surface, overlay_content_size.x, overlay_content_size.y)) {
this->texture = nullptr;
this->texture_width = 0;
this->texture_height = 0;
surface->Release();
return;
}
}
IDirect3DSurface9 *texture_surface = nullptr;
hr = this->texture->GetSurfaceLevel(0, &texture_surface);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to get texture surface, hr={}", FMT_HRESULT(hr));
surface->Release();
return;
}
hr = this->device->StretchRect(surface, nullptr, texture_surface, nullptr, D3DTEXF_LINEAR);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to copy back buffer contents, hr={}", FMT_HRESULT(hr));
surface->Release();
texture_surface->Release();
return;
}
surface->Release();
texture_surface->Release();
// draw the subscreen (this draws *under* ImGui windows, over the game surface)
auto bottom_right = overlay_content_size;
bottom_right.x += overlay_content_top_left.x;
bottom_right.y += overlay_content_top_left.y;
ImGui::GetBackgroundDrawList()->AddImage(
reinterpret_cast<void *>(this->texture),
overlay_content_top_left,
bottom_right);
if (this->draws_window) {
// draw an invisible button so that it swallows mouse input
// this is needed to prevent the window from being dragged around
// (alternatively io.ConfigWindowsMoveFromTitleBarOnly can be set but that is global)
ImGui::InvisibleButton(
(this->title + "__InvisibleButton").c_str(),
overlay_content_size,
ImGuiButtonFlags_None);
}
#if OVERLAYDBG
log_info("sub::overlay", "{} / {} = {}", overlay_content_size.x, overlay_content_size.y, overlay_content_size.x / overlay_content_size.y);
#endif
}
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef SPICETOOLS_OVERLAY_WINDOWS_GENERIC_SUB_H
#define SPICETOOLS_OVERLAY_WINDOWS_GENERIC_SUB_H
#include <optional>
#include <windows.h>
#include <d3d9.h>
#include "overlay/window.h"
namespace overlay::windows {
class GenericSubScreen : public Window {
public:
GenericSubScreen(SpiceOverlay *overlay);
void build_content() override;
protected:
virtual void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out);
ImVec2 overlay_content_top_left;
ImVec2 overlay_content_size;
private:
static void keep_16_by_9(ImGuiSizeCallbackData* data) {
data->DesiredSize.y = (data->DesiredSize.x * 9.f / 16.f) + ImGui::GetFrameHeight();
}
bool build_texture(IDirect3DSurface9 *surface, UINT width, UINT height);
void draw_texture();
std::optional<std::string> status_message = std::nullopt;
IDirect3DDevice9 *device = nullptr;
IDirect3DTexture9 *texture = nullptr;
UINT texture_width;
UINT texture_height;
};
}
#endif // SPICETOOLS_OVERLAY_WINDOWS_GENERIC_SUB_H
+148
View File
@@ -0,0 +1,148 @@
#include <map>
#include "iidx_seg.h"
#include "games/io.h"
#include "games/iidx/iidx.h"
#include "util/logging.h"
namespace overlay::windows {
uint32_t IIDX_SEGMENT_FONT_SIZE = 64;
std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR = std::nullopt;
std::string IIDX_SEGMENT_LOCATION = "bottom";
static const size_t TICKER_SIZE = 9;
static const ImVec4 DARK_GRAY(0.1f, 0.1f, 0.1f, 1.f);
static const ImVec4 RED(1.f, 0.f, 0.f, 1.f);
static const int PADDING_Y = 8;
static const int PADDING_X = 4;
static const std::map<char, std::pair<char, char>> CHARMAP = {\
// period - add a space afterwards (game sends 'm' for period)
{'m', {'.', ' '}},
// exclamation mark (use ./ to make it look like one)
{'!', {'.', '/'}},
// font doesn't have tilde so using a dash instead
{'~', {'-', '\0'}},
};
IIDXSegmentDisplay::IIDXSegmentDisplay(SpiceOverlay *overlay) : Window(overlay) {
if (!DSEG_FONT) {
log_fatal("iidx_seg", "DSEG_FONT is null");
}
this->title = "IIDX LED Segment Display";
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
this->remove_window_padding = true;
this->size_max = ImVec2(ImGui::GetIO().DisplaySize.x, ImGui::GetIO().DisplaySize.y);
this->flags = ImGuiWindowFlags_NoTitleBar
| ImGuiWindowFlags_NoScrollbar
| ImGuiWindowFlags_NoResize
| ImGuiWindowFlags_NoNavFocus
| ImGuiWindowFlags_NoNavInputs
| ImGuiWindowFlags_NoDocking;
if (IIDX_SEGMENT_FONT_COLOR.has_value()) {
const auto rgb = IIDX_SEGMENT_FONT_COLOR.value();
const auto rgba = IM_COL32_A_MASK | rgb;
this->color = ImGui::ColorConvertU32ToFloat4(rgba);
} else {
this->color = RED;
}
}
void IIDXSegmentDisplay::calculate_initial_window() {
// ImGui::CalcTextSize doesn't seem to work here, so manually calculate
ImGui::PushFont(DSEG_FONT);
this->init_size = ImGui::CalcTextSize("~.~.~.~.~.~.~.~.~.");
this->init_size.x += PADDING_X * 2;
this->init_size.y += PADDING_Y * 2;
ImGui::PopFont();
// initial horizontal position
if (IIDX_SEGMENT_LOCATION.find("left") != std::string::npos) {
this->init_pos.x = 0;
} else if (IIDX_SEGMENT_LOCATION.find("right") != std::string::npos) {
this->init_pos.x = ImGui::GetIO().DisplaySize.x - this->init_size.x;
} else {
// center
this->init_pos.x = ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2;
}
// initial vertical position
if (IIDX_SEGMENT_LOCATION.rfind("top", 0) == 0) {
this->init_pos.y = 0;
} else {
// bottom
this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y;
}
}
void IIDXSegmentDisplay::build_content() {
char input_ticker[TICKER_SIZE];
// get ticker content from game
games::iidx::IIDX_LED_TICKER_LOCK.lock();
memcpy(input_ticker, games::iidx::IIDXIO_LED_TICKER, TICKER_SIZE);
games::iidx::IIDX_LED_TICKER_LOCK.unlock();
// since every input character can result in up to two characters,
// need double size for output
const size_t TICKER_OUTPUT_SIZE = TICKER_SIZE * 2 + 1;
char output_ticker[TICKER_OUTPUT_SIZE];
int output_ticker_index = 0;
// look at each input char and convert into output char(s)
for (const auto c : input_ticker) {
// see if there is an applicable rule for this input character
if (0 == CHARMAP.count(c)) {
// if not, copy the first character and keep going
output_ticker[output_ticker_index] = c;
output_ticker_index += 1;
continue;
}
// there is a replacement rule for this input character
auto replacements = CHARMAP.at(c);
// replace the first character...
output_ticker[output_ticker_index] = replacements.first;
output_ticker_index += 1;
// and optionally add the second character
if (replacements.second != '\0') {
output_ticker[output_ticker_index] = replacements.second;
output_ticker_index += 1;
}
}
if ((int)TICKER_OUTPUT_SIZE <= output_ticker_index) {
log_fatal("iidx_seg", "{} is beyond array bounds", output_ticker_index);
}
// terminating null...
output_ticker[output_ticker_index] = '\0';
// finally, draw UI elements
draw_ticker(output_ticker);
}
void IIDXSegmentDisplay::draw_ticker(char *ticker_string) {
ImGui::PushFont(DSEG_FONT);
const auto pos = ImVec2(PADDING_X, PADDING_Y);
// to imitate LED "off"
ImGui::SetCursorPos(pos);
ImGui::PushStyleColor(ImGuiCol_Text, DARK_GRAY);
ImGui::TextUnformatted("~.~.~.~.~.~.~.~.~.");
ImGui::PopStyleColor();
// ... then draw the LED "on" above it
ImGui::SetCursorPos(pos);
ImGui::PushStyleColor(ImGuiCol_Text, this->color);
ImGui::TextUnformatted(ticker_string);
ImGui::PopStyleColor();
ImGui::PopFont();
}
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <optional>
#include "overlay/window.h"
namespace overlay::windows {
extern uint32_t IIDX_SEGMENT_FONT_SIZE;
extern std::optional<uint32_t> IIDX_SEGMENT_FONT_COLOR;
extern std::string IIDX_SEGMENT_LOCATION;
class IIDXSegmentDisplay : public Window {
public:
IIDXSegmentDisplay(SpiceOverlay *overlay);
void calculate_initial_window() override;
void build_content() override;
private:
ImVec4 color;
void draw_ticker(char *ticker_string);
};
}
+27 -91
View File
@@ -1,107 +1,43 @@
#undef CINTERFACE
#include "iidx_sub.h"
#include <fmt/format.h>
#include "games/io.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "hooks/graphics/backends/d3d9/d3d9_device.h"
#include "util/logging.h"
#include "games/iidx/iidx.h"
namespace overlay::windows {
const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
const ImVec4 WHITE(1.f, 1.f, 1.f, 1.f);
IIDXSubScreen::IIDXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "IIDX Sub Screen";
IIDXSubScreen::IIDXSubScreen(SpiceOverlay *overlay) : Window(overlay), device(overlay->get_device()) {
float size = 0.5f;
if (games::iidx::SUBSCREEN_OVERLAY_SIZE.has_value()) {
if (games::iidx::SUBSCREEN_OVERLAY_SIZE.value() == "large") {
size = 0.8f;
} else if (games::iidx::SUBSCREEN_OVERLAY_SIZE.value() == "small") {
size = 0.3f;
} else if (games::iidx::SUBSCREEN_OVERLAY_SIZE.value() == "fullscreen") {
this->draws_window = false;
this->title = "Sub Screen";
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
}
}
this->init_size = ImVec2(
ImGui::GetIO().DisplaySize.x * size,
(ImGui::GetIO().DisplaySize.x * size * 9 / 16) + ImGui::GetFrameHeight());
this->texture_size = ImVec2(0, 0);
this->size_max = ImVec2(
ImGui::GetIO().DisplaySize.x - ImGui::GetFrameHeight() * 2,
ImGui::GetIO().DisplaySize.y - ImGui::GetFrameHeight() * 2);
// middle / bottom
this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
ImGui::GetIO().DisplaySize.y - this->init_size.y - (ImGui::GetFrameHeight() / 2));
}
void IIDXSubScreen::build_content() {
this->draw_texture();
/*
if (this->status_message.has_value()) {
ImGui::TextColored(YELLOW, "%s", this->status_message.value().c_str());
} else if (this->texture) {
ImGui::TextColored(WHITE, "Successfully acquired surface texture");
} else {
ImGui::TextColored(YELLOW, "Failed to acquire surface texture");
}
*/
}
bool IIDXSubScreen::build_texture(IDirect3DSurface9 *surface) {
HRESULT hr;
D3DSURFACE_DESC desc {};
hr = surface->GetDesc(&desc);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to get surface descriptor, hr={}", FMT_HRESULT(hr));
return false;
}
hr = this->device->CreateTexture(desc.Width, desc.Height, 0, desc.Usage, desc.Format,
desc.Pool, &this->texture, nullptr);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to create render target, hr={}", FMT_HRESULT(hr));
return false;
}
this->texture_size = ImVec2(desc.Width, desc.Height);
return true;
}
void IIDXSubScreen::draw_texture() {
HRESULT hr;
auto surface = graphics_d3d9_ldj_get_sub_screen();
if (surface == nullptr) {
void IIDXSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
if (!this->get_active()) {
return;
}
if (this->texture == nullptr) {
if (!this->build_texture(surface)) {
this->texture = nullptr;
this->texture_size = ImVec2(0, 0);
surface->Release();
return;
}
}
IDirect3DSurface9 *texture_surface = nullptr;
hr = this->texture->GetSurfaceLevel(0, &texture_surface);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to get texture surface, hr={}", FMT_HRESULT(hr));
surface->Release();
return;
}
hr = this->device->StretchRect(surface, nullptr, texture_surface, nullptr, D3DTEXF_NONE);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to copy back buffer contents, hr={}", FMT_HRESULT(hr));
surface->Release();
texture_surface->Release();
return;
}
surface->Release();
texture_surface->Release();
ImGui::GetBackgroundDrawList()->AddImage(
reinterpret_cast<void *>(this->texture),
ImVec2(0, 0),
this->texture_size,
ImVec2(0, 0),
ImVec2(1, 1));
*x_out = xy_in.x * ImGui::GetIO().DisplaySize.x;
*y_out = xy_in.y * ImGui::GetIO().DisplaySize.y;
}
}
+4 -17
View File
@@ -1,30 +1,17 @@
#ifndef SPICETOOLS_OVERLAY_WINDOWS_IIDX_SUB_H
#define SPICETOOLS_OVERLAY_WINDOWS_IIDX_SUB_H
#include <optional>
#include <windows.h>
#include <d3d9.h>
#include "overlay/window.h"
#include "overlay/windows/generic_sub.h"
namespace overlay::windows {
class IIDXSubScreen : public Window {
class IIDXSubScreen : public GenericSubScreen {
public:
IIDXSubScreen(SpiceOverlay *overlay);
void build_content() override;
private:
bool build_texture(IDirect3DSurface9 *surface);
void draw_texture();
std::optional<std::string> status_message = std::nullopt;
IDirect3DDevice9 *device = nullptr;
IDirect3DTexture9 *texture = nullptr;
ImVec2 texture_size;
protected:
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
};
}
+121
View File
@@ -0,0 +1,121 @@
#include "iopanel.h"
#include "cfg/api.h"
#include "launcher/launcher.h"
#include "games/io.h"
#include "misc/eamuse.h"
namespace overlay::windows {
IOPanel::IOPanel(SpiceOverlay *overlay) : Window(overlay) {
this->title = "Operator";
this->flags = ImGuiWindowFlags_AlwaysAutoResize;
this->init_size = ImVec2(
80,
ImGui::GetFrameHeight() +
ImGui::GetStyle().WindowPadding.y * 2 +
ImGui::GetFrameHeightWithSpacing() * 3 +
ImGui::GetFrameHeight()); // title + windows padding + 4 buttons
this->init_pos =ImVec2(10, ImGui::GetIO().DisplaySize.y - this->init_size.y - 10);
this->toggle_button = games::OverlayButtons::ToggleIOPanel;
this->find_buttons();
}
void IOPanel::find_buttons() {
const auto buttons = games::get_buttons(eamuse_get_game());
for (auto &button : *buttons) {
// since the array indices are different for each game, use string match instead
if (button.getName() == "Service") {
this->service_button = &button;
} else if (button.getName() == "Test") {
this->test_button = &button;
}
}
}
float IOPanel::get_suggested_height() {
// height of 4 buttons stacked vertically
// (coin, unlock, service, test, plus spacing between them)
return ImGui::GetFrameHeightWithSpacing() * 3 + ImGui::GetFrameHeight();
}
void IOPanel::build_content() {
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(0.2f, 0.1f, 0.1f, 1.f));
ImGui::PushID(this);
ImGui::BeginGroup();
this->build_operator_menu();
ImGui::EndGroup();
ImGui::SameLine();
this->build_io_panel();
ImGui::PopID();
ImGui::PopStyleColor();
}
void IOPanel::build_io_panel() {}
void IOPanel::build_operator_menu() {
const ImVec2 wide(60, 0);
const float spacing_x = ImGui::GetStyle().ItemSpacing.y;
const ImVec2 tall(
ImGui::GetFrameHeight(),
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight());
if (ImGui::Button("COIN", ImVec2(wide.x + spacing_x + tall.x, wide.y))) {
eamuse_coin_add();
}
ImGui::Checkbox("unlock", &operator_unlocked);
ImGui::BeginDisabled(!operator_unlocked);
this->build_button("SERVICE", wide, this->service_button);
this->build_button("TEST", wide, this->test_button);
// service + test button (for test menu navigation)
ImGui::SameLine(0, spacing_x);
ImGui::BeginGroup();
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() - ImGui::GetFrameHeightWithSpacing());
this->build_button("+", tall, this->test_button, this->service_button);
}
ImGui::EndGroup();
ImGui::EndDisabled();
}
void IOPanel::build_button(
const char *label, const ImVec2 &size, Button *button, Button *button_alt, Light *light) {
ImGui::BeginDisabled(button == nullptr);
int pushed = 0;
if (light && 0.5f < GameAPI::Lights::readLight(RI_MGR, *light)) {
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.72f, 0.15f, 0.00f, 0.9f));
pushed += 1;
}
ImGui::Button(label, size);
if (ImGui::IsItemActivated()) {
button->override_state = GameAPI::Buttons::BUTTON_PRESSED;
button->override_velocity = 1.f;
button->override_enabled = true;
if (button_alt) {
button_alt->override_state = GameAPI::Buttons::BUTTON_PRESSED;
button_alt->override_velocity = 1.f;
button_alt->override_enabled = true;
}
} else if (ImGui::IsItemDeactivated()) {
button->override_enabled = false;
if (button_alt) {
button_alt->override_enabled = false;
}
}
ImGui::PopStyleColor(pushed);
ImGui::EndDisabled();
}
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include "overlay/window.h"
#include "cfg/button.h"
namespace overlay::windows {
class IOPanel : public Window {
public:
IOPanel(SpiceOverlay *overlay);
void build_content() override;
protected:
virtual void build_io_panel();
void build_button(
const char *label,
const ImVec2 &size,
Button *button,
Button *button_alt = nullptr,
Light *light = nullptr);
float get_suggested_height();
private:
void find_buttons();
void build_operator_menu();
bool operator_unlocked = false;
Button *test_button = nullptr;
Button *service_button = nullptr;
};
}
+95
View File
@@ -0,0 +1,95 @@
#include "iopanel_ddr.h"
#include "games/io.h"
#include "games/ddr/io.h"
#include "misc/eamuse.h"
#include "util/logging.h"
namespace overlay::windows {
DDRIOPanel::DDRIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
this->title = "DDR IO Panel";
find_ddr_buttons();
}
void DDRIOPanel::find_ddr_buttons() {
const auto buttons = games::get_buttons(eamuse_get_game());
const auto lights = games::get_lights(eamuse_get_game());
// SD cabs don't have lights for these buttons, so just use the HD ones
this->start[0] = &(*buttons)[games::ddr::Buttons::P1_START];
this->up[0] = &(*buttons)[games::ddr::Buttons::P1_MENU_UP];
this->down[0] = &(*buttons)[games::ddr::Buttons::P1_MENU_DOWN];
this->left[0] = &(*buttons)[games::ddr::Buttons::P1_MENU_LEFT];
this->right[0] = &(*buttons)[games::ddr::Buttons::P1_MENU_RIGHT];
this->start_light[0] = &(*lights)[games::ddr::Lights::HD_P1_START];
this->updown_light[0] = &(*lights)[games::ddr::Lights::HD_P1_UP_DOWN];
this->leftright_light[0] = &(*lights)[games::ddr::Lights::HD_P1_LEFT_RIGHT];
this->start[1] = &(*buttons)[games::ddr::Buttons::P2_START];
this->up[1] = &(*buttons)[games::ddr::Buttons::P2_MENU_UP];
this->down[1] = &(*buttons)[games::ddr::Buttons::P2_MENU_DOWN];
this->left[1] = &(*buttons)[games::ddr::Buttons::P2_MENU_LEFT];
this->right[1] = &(*buttons)[games::ddr::Buttons::P2_MENU_RIGHT];
this->start_light[1] = &(*lights)[games::ddr::Lights::HD_P2_START];
this->updown_light[1] = &(*lights)[games::ddr::Lights::HD_P2_UP_DOWN];
this->leftright_light[1] = &(*lights)[games::ddr::Lights::HD_P2_LEFT_RIGHT];
}
void DDRIOPanel::build_io_panel() {
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
this->draw_buttons(0);
ImGui::SameLine();
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
this->draw_buttons(1);
}
void DDRIOPanel::draw_buttons(const int p) {
// 2x2
const ImVec2 start_button_size(
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight(),
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight()
);
// 2x1
const ImVec2 updown_size(start_button_size.x, ImGui::GetFrameHeight());
// 1x2
const ImVec2 leftright_size(ImGui::GetFrameHeight(), start_button_size.y);
ImGui::BeginGroup();
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
this->build_button("<", leftright_size, this->left[p], nullptr, this->leftright_light[p]);
}
ImGui::EndGroup();
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.y);
ImGui::BeginGroup();
{
this->build_button("^", updown_size, this->up[p], nullptr, this->updown_light[p]);
const char *label;
if (p == 0) {
label = " 1 P\nStart";
} else {
label = " 2 P\nStart";
}
this->build_button(label, start_button_size, this->start[p], nullptr, this->start_light[p]);
this->build_button("v", updown_size, this->down[p], nullptr, this->updown_light[p]);
}
ImGui::EndGroup();
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.y);
ImGui::BeginGroup();
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
this->build_button(">", leftright_size, this->right[p], nullptr, this->leftright_light[p]);
}
ImGui::EndGroup();
}
}
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include "overlay/window.h"
#include "overlay/windows/iopanel.h"
#include "cfg/button.h"
namespace overlay::windows {
class DDRIOPanel : public IOPanel {
public:
DDRIOPanel(SpiceOverlay *overlay);
protected:
void build_io_panel() override;
private:
void find_ddr_buttons();
void draw_buttons(const int player);
Button *start[2];
Button *up[2];
Button *down[2];
Button *left[2];
Button *right[2];
Light *start_light[2];
Light *updown_light[2];
Light *leftright_light[2];
};
}
+163
View File
@@ -0,0 +1,163 @@
#include "iopanel_gfdm.h"
#include "avs/game.h"
#include "games/io.h"
#include "games/gitadora/gitadora.h"
#include "games/gitadora/io.h"
#include "misc/eamuse.h"
#include "util/logging.h"
namespace overlay::windows {
GitadoraIOPanel::GitadoraIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
this->title = "GITADORA IO Panel";
// by default, make a safer assumption that there are two players
this->two_players = true;
// by default, enable the extra input only available on DX cabs...
this->has_guitar_knobs = true;
// drummania can only have one player, no guitar knobs
if (avs::game::is_model({ "J32", "K32", "L32" }) ||
(avs::game::is_model("M32") && avs::game::SPEC[0] == 'B')) {
this->two_players = false;
this->has_guitar_knobs = false;
}
// and cab type 3 (white cab) only has one guitar
if (games::gitadora::CAB_TYPE.has_value() && games::gitadora::CAB_TYPE == 3) {
this->two_players = false;
}
// disable guitar knobs on known non-DX cabs
if (games::gitadora::CAB_TYPE.has_value() &&
(games::gitadora::CAB_TYPE == 2 || games::gitadora::CAB_TYPE == 3)) {
this->has_guitar_knobs = false;
}
find_gfdm_buttons();
}
void GitadoraIOPanel::find_gfdm_buttons() {
const auto buttons = games::get_buttons(eamuse_get_game());
// device emulation treats drum controls to be the same as guitar 1p
this->start[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Start];
this->help[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Help];
this->up[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Up];
this->down[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Down];
this->left[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Left];
this->right[0] = &(*buttons)[games::gitadora::Buttons::GuitarP1Right];
this->start[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Start];
this->help[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Help];
this->up[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Up];
this->down[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Down];
this->left[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Left];
this->right[1] = &(*buttons)[games::gitadora::Buttons::GuitarP2Right];
}
void GitadoraIOPanel::build_io_panel() {
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
this->draw_buttons(0);
if (this->has_guitar_knobs) {
ImGui::SameLine();
this->draw_sliders(0);
}
// draw p2 only if guitar freaks
if (this->two_players) {
ImGui::SameLine();
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
this->draw_buttons(1);
if (this->has_guitar_knobs) {
ImGui::SameLine();
this->draw_sliders(1);
}
}
}
void GitadoraIOPanel::draw_buttons(const int p) {
// 2x2
const ImVec2 start_button_size(
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight(),
ImGui::GetFrameHeightWithSpacing() + ImGui::GetFrameHeight()
);
// 2x1
const ImVec2 updown_size(start_button_size.x, ImGui::GetFrameHeight());
// 1x2
const ImVec2 leftright_size(ImGui::GetFrameHeight(), start_button_size.y);
// 1x1
const ImVec2 tiny_size(ImGui::GetFrameHeight(), ImGui::GetFrameHeight());
ImGui::BeginGroup();
{
ImGui::SetCursorPosY(ImGui::GetCursorPosY() + ImGui::GetFrameHeightWithSpacing());
this->build_button("<", leftright_size, this->left[p]);
}
ImGui::EndGroup();
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.y);
ImGui::BeginGroup();
{
this->build_button("^", updown_size, this->up[p]);
const char *label;
if (this->two_players) {
if (p == 0) {
label = " 1 P\nStart";
} else {
label = " 2 P\nStart";
}
} else {
label = "Start";
}
this->build_button(label, start_button_size, this->start[p]);
this->build_button("v", updown_size, this->down[p]);
}
ImGui::EndGroup();
ImGui::SameLine(0, ImGui::GetStyle().ItemSpacing.y);
ImGui::BeginGroup();
{
this->build_button("?", tiny_size, this->help[p]);
this->build_button(">", leftright_size, this->right[p]);
}
ImGui::EndGroup();
}
void GitadoraIOPanel::draw_sliders(const int p) {
const auto index =
(p == 0) ?
games::gitadora::Analogs::GuitarP1Knob :
games::gitadora::Analogs::GuitarP2Knob;
const ImVec2 slider_size(32, get_suggested_height());
// get analog
const auto analogs = games::get_analogs(eamuse_get_game());
auto &analog = (*analogs)[index];
// vertical slider
auto new_state = analog.override_enabled ? analog.override_state
: GameAPI::Analogs::getState(RI_MGR, analog);
ImGui::PushID(index);
ImGui::VSliderFloat(
"##v",
slider_size,
&new_state,
0.f, 1.f,
"Knob",
ImGuiSliderFlags_AlwaysClamp);
ImGui::PopID();
if (new_state != analog.override_state) {
analog.override_state = new_state;
analog.override_enabled = true;
}
}
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "overlay/window.h"
#include "overlay/windows/iopanel.h"
#include "cfg/button.h"
namespace overlay::windows {
class GitadoraIOPanel : public IOPanel {
public:
GitadoraIOPanel(SpiceOverlay *overlay);
protected:
void build_io_panel() override;
private:
void find_gfdm_buttons();
void draw_buttons(const int player);
void draw_sliders(const int player);
bool two_players;
bool has_guitar_knobs;
Button *start[2];
Button *help[2];
Button *up[2];
Button *down[2];
Button *left[2];
Button *right[2];
};
}
+168
View File
@@ -0,0 +1,168 @@
#include <map>
#include "iopanel_iidx.h"
#include "games/io.h"
#include "games/iidx/iidx.h"
#include "games/iidx/io.h"
#include "misc/eamuse.h"
#include "util/logging.h"
namespace overlay::windows {
IIDXIOPanel::IIDXIOPanel(SpiceOverlay *overlay) : IOPanel(overlay) {
this->title = "IIDX IO Panel";
find_iidx_buttons();
}
void IIDXIOPanel::find_iidx_buttons() {
const auto buttons = games::get_buttons(eamuse_get_game());
const auto lights = games::get_lights(eamuse_get_game());
this->start_1p = &(*buttons)[games::iidx::Buttons::P1_Start];
this->start_1p_light = &(*lights)[games::iidx::Lights::P1_Start];
this->start_2p = &(*buttons)[games::iidx::Buttons::P2_Start];
this->start_2p_light = &(*lights)[games::iidx::Lights::P2_Start];
this->vefx = &(*buttons)[games::iidx::Buttons::VEFX];
this->vefx_light = &(*lights)[games::iidx::Lights::VEFX];
this->effect_on = &(*buttons)[games::iidx::Buttons::Effect];
this->effect_on_light = &(*lights)[games::iidx::Lights::Effect];
}
void IIDXIOPanel::build_io_panel() {
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
this->draw_buttons();
if (!games::iidx::TDJ_MODE) {
ImGui::SameLine();
ImGui::Dummy(ImVec2(12, 0));
ImGui::SameLine();
this->draw_sliders();
}
}
void IIDXIOPanel::draw_buttons() {
const auto big_button_size = ImVec2(get_suggested_height(), get_suggested_height());
// width = same as above,
// height = 2 rows of buttons
const auto mid_button_size = ImVec2(
big_button_size.x,
ImGui::GetFrameHeight() * 2 + ImGui::GetStyle().ItemSpacing.y
);
// width = half of above,
// height = 4 rows of buttons
const auto tall_button_size = ImVec2(
big_button_size.x / 2,
big_button_size.y
);
this->build_button(
" 1 P\n"
"Start",
big_button_size,
this->start_1p, nullptr,
this->start_1p_light);
ImGui::SameLine();
ImGui::BeginGroup();
this->build_button(
"EFFECT",
mid_button_size,
this->effect_on, nullptr,
this->effect_on_light);
this->build_button(
"VEFX",
mid_button_size,
this->vefx, nullptr,
this->vefx_light);
ImGui::EndGroup();
ImGui::SameLine();
this->build_button(
"EFF\n"
" +\n"
"VFX",
tall_button_size,
this->effect_on,
this->vefx);
ImGui::SameLine();
this->build_button(
" 2 P\n"
"Start",
big_button_size,
this->start_2p, nullptr,
this->start_2p_light);
}
void IIDXIOPanel::draw_sliders() {
// effector analog entries
static const std::map<size_t, const char *> ANALOG_ENTRIES {
{ games::iidx::Analogs::VEFX, "VEFX" },
{ games::iidx::Analogs::LowEQ, "LoEQ" },
{ games::iidx::Analogs::HiEQ, "HiEQ" },
{ games::iidx::Analogs::Filter, "Fltr" },
{ games::iidx::Analogs::PlayVolume, "Vol." },
};
const ImVec2 slider_size(32, get_suggested_height());
const auto reset_button_size = ImVec2(16, get_suggested_height());
// iterate analogs
bool overridden = false;
const auto analogs = games::get_analogs(eamuse_get_game());
for (auto &[index, name] : ANALOG_ENTRIES) {
// safety check
if (index >= analogs->size()) {
continue;
}
// get analog
auto &analog = (*analogs)[index];
overridden |= analog.override_enabled;
// push id and style
ImGui::PushID((void *) name);
// vertical slider
auto new_state = analog.override_enabled ? analog.override_state
: GameAPI::Analogs::getState(RI_MGR, analog);
if (0 < index) {
ImGui::SameLine();
}
ImGui::VSliderFloat(
"##v",
slider_size,
&new_state,
0.f, 1.f,
name,
ImGuiSliderFlags_AlwaysClamp);
if (new_state != analog.override_state) {
analog.override_state = new_state;
analog.override_enabled = true;
}
// pop id and style
ImGui::PopID();
}
// reset button
ImGui::SameLine();
if (ImGui::Button("r\ne\ns\ne\nt", reset_button_size)) {
for (auto &[index, name] : ANALOG_ENTRIES) {
if (index < analogs->size()) {
(*analogs)[index].override_enabled = false;
}
}
}
}
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include "overlay/window.h"
#include "overlay/windows/iopanel.h"
#include "cfg/button.h"
namespace overlay::windows {
class IIDXIOPanel : public IOPanel {
public:
IIDXIOPanel(SpiceOverlay *overlay);
protected:
void build_io_panel() override;
private:
void find_iidx_buttons();
void draw_buttons();
void draw_sliders();
Button *start_1p = nullptr;
Light *start_1p_light = nullptr;
Button *start_2p = nullptr;
Light *start_2p_light = nullptr;
Button *effect_on = nullptr;
Light *effect_on_light = nullptr;
Button *vefx = nullptr;
Light *vefx_light = nullptr;
};
}
+1 -1
View File
@@ -77,7 +77,7 @@ namespace overlay::windows {
}
// mouse down handler
if (ImGui::IsItemHovered() && ImGui::IsAnyMouseDown()) {
if (ImGui::IsItemActive()) {
eamuse_set_keypad_overrides_overlay(this->unit, button.flag);
}
+152 -12
View File
@@ -1,18 +1,20 @@
#include <games/io.h>
#include "screen_resize.h"
#include "avs/game.h"
#include "cfg/screen_resize.h"
#include "hooks/graphics/graphics.h"
#include "overlay/imgui/extensions.h"
#include "misc/eamuse.h"
#include "util/logging.h"
#include "util/utils.h"
namespace overlay::windows {
ScreenResize::ScreenResize(SpiceOverlay *overlay) : Window(overlay) {
this->title = "Screen Resize";
this->flags = ImGuiWindowFlags_AlwaysAutoResize;
this->init_pos = ImVec2(
ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2,
ImGui::GetIO().DisplaySize.y / 2 - this->init_size.y / 2);
this->init_pos = ImVec2(10, 10);
this->toggle_button = games::OverlayButtons::ToggleScreenResize;
this->toggle_screen_resize = games::OverlayButtons::ScreenResize;
}
@@ -20,16 +22,86 @@ namespace overlay::windows {
ScreenResize::~ScreenResize() {
}
void ScreenResize::build_content() {
HWND ScreenResize::get_first_window() {
if (GRAPHICS_WINDOWS.size() == 0) {
return NULL;
}
return GRAPHICS_WINDOWS[0];
}
void ScreenResize::reset_window() {
this->reset_vars_to_default();
if (GRAPHICS_WINDOWED) {
const auto window = get_first_window();
if (window) {
graphics_move_resize_window(window);
graphics_update_window_style(window);
}
}
}
void ScreenResize::reset_vars_to_default() {
cfg::SCREENRESIZE->enable_screen_resize = false;
cfg::SCREENRESIZE->enable_linear_filter = true;
cfg::SCREENRESIZE->keep_aspect_ratio = true;
cfg::SCREENRESIZE->centered = true;
cfg::SCREENRESIZE->offset_x = 0;
cfg::SCREENRESIZE->offset_y = 0;
cfg::SCREENRESIZE->scale_x = 1.f;
cfg::SCREENRESIZE->scale_y = 1.f;
cfg::SCREENRESIZE->enable_window_resize = false;
cfg::SCREENRESIZE->window_always_on_top = false;
cfg::SCREENRESIZE->window_decoration = cfg::WindowDecorationMode::Default;
cfg::SCREENRESIZE->window_offset_x = 0;
cfg::SCREENRESIZE->window_offset_y = 0;
cfg::SCREENRESIZE->client_keep_aspect_ratio = true;
cfg::SCREENRESIZE->client_width = cfg::SCREENRESIZE->init_client_width;
cfg::SCREENRESIZE->client_height = cfg::SCREENRESIZE->init_client_height;
}
void ScreenResize::build_content() {
ImGui::Text("For: %s", eamuse_get_game().c_str());
{
int flags = 0;
if (!GRAPHICS_WINDOWED || cfg::SCREENRESIZE->enable_screen_resize) {
flags |= ImGuiTreeNodeFlags_DefaultOpen;
}
if (ImGui::TreeNodeEx("Image Resize", flags)) {
this->build_fullscreen_config();
ImGui::TreePop();
}
}
ImGui::BeginDisabled(!GRAPHICS_WINDOWED);
{
int flags = 0;
if (GRAPHICS_WINDOWED) {
flags |= ImGuiTreeNodeFlags_DefaultOpen;
}
if (ImGui::TreeNodeEx("Window Size", flags)) {
build_windowed_config();
ImGui::TreePop();
}
ImGui::EndDisabled();
}
ImGui::Separator();
build_footer();
}
void ScreenResize::build_fullscreen_config() {
// enable checkbox
ImGui::Checkbox("Enable Screen Resize", &cfg::SCREENRESIZE->enable_screen_resize);
if (cfg::SCREENRESIZE->enable_screen_resize) {
ImGui::Checkbox("Enable", &cfg::SCREENRESIZE->enable_screen_resize);
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_screen_resize);
// general settings
ImGui::Checkbox("Enable Linear Filter", &cfg::SCREENRESIZE->enable_linear_filter);
ImGui::Checkbox("Linear Filter", &cfg::SCREENRESIZE->enable_linear_filter);
ImGui::Checkbox("Centered", &cfg::SCREENRESIZE->centered);
if (!cfg::SCREENRESIZE->centered) {
ImGui::InputInt("X Offset", &cfg::SCREENRESIZE->offset_x);
ImGui::InputInt("Y Offset", &cfg::SCREENRESIZE->offset_y);
}
// aspect ratio
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->keep_aspect_ratio);
@@ -42,13 +114,81 @@ namespace overlay::windows {
ImGui::SliderFloat("Height Scale", &cfg::SCREENRESIZE->scale_y, 0.65f, 2.0f);
}
ImGui::EndDisabled();
}
void ScreenResize::build_windowed_config() {
// for now, only supports the first window
const auto window = get_first_window();
if (!window) {
return;
}
ImGui::TextUnformatted("Warning: may cause some games to crash.");
ImGui::BeginDisabled(graphics_window_change_crashes_game());
if (ImGui::Combo(
"Window Style",
&cfg::SCREENRESIZE->window_decoration,
"Default\0Borderless\0Resizable Window\0\0")) {
graphics_update_window_style(window);
}
ImGui::EndDisabled();
ImGui::SameLine();
ImGui::HelpMarker(
"Change window decoration. Resizable Window may not cause your mouse cursor to change, "
"but you can still drag to resize. Disabled for some games due to incompatibility.");
if (ImGui::Checkbox("Always on Top", &cfg::SCREENRESIZE->window_always_on_top) ) {
graphics_update_z_order(window);
}
ImGui::Checkbox("Keep Aspect Ratio", &cfg::SCREENRESIZE->client_keep_aspect_ratio);
ImGui::Checkbox("Manual window move/resize", &cfg::SCREENRESIZE->enable_window_resize);
ImGui::BeginDisabled(!cfg::SCREENRESIZE->enable_window_resize);
bool changed = false;
const uint32_t step = 1;
const uint32_t step_fast = 10;
ImGui::BeginDisabled(cfg::SCREENRESIZE->client_keep_aspect_ratio);
changed |= ImGui::InputScalar(
"Width",
ImGuiDataType_U32,
&cfg::SCREENRESIZE->client_width,
&step, &step_fast, nullptr,
ImGuiInputTextFlags_EnterReturnsTrue);
ImGui::EndDisabled();
changed |= ImGui::InputScalar(
"Height",
ImGuiDataType_U32,
&cfg::SCREENRESIZE->client_height,
&step, &step_fast, nullptr,
ImGuiInputTextFlags_EnterReturnsTrue);
changed |= ImGui::InputScalar(
"X Offset",
ImGuiDataType_S32,
&cfg::SCREENRESIZE->window_offset_x,
&step, &step_fast, nullptr,
ImGuiInputTextFlags_EnterReturnsTrue);
changed |= ImGui::InputScalar(
"Y Offset",
ImGuiDataType_S32,
&cfg::SCREENRESIZE->window_offset_y,
&step, &step_fast, nullptr,
ImGuiInputTextFlags_EnterReturnsTrue);
if (changed) {
if (cfg::SCREENRESIZE->client_keep_aspect_ratio) {
cfg::SCREENRESIZE->client_width =
cfg::SCREENRESIZE->client_height * cfg::SCREENRESIZE->init_client_aspect_ratio;
}
graphics_move_resize_window(window);
}
ImGui::EndDisabled();
}
void ScreenResize::build_footer() {
// reset button
if (ImGui::Button("Reset")) {
cfg::SCREENRESIZE->offset_x = 0;
cfg::SCREENRESIZE->offset_y = 0;
cfg::SCREENRESIZE->scale_x = 1;
cfg::SCREENRESIZE->scale_y = 1;
}
this->reset_window();
}
// load button
+14
View File
@@ -15,5 +15,19 @@ namespace overlay::windows {
private:
size_t toggle_screen_resize = ~0u;
bool toggle_screen_resize_state = false;
void build_fullscreen_config();
void build_windowed_config();
void build_footer();
std::string hwnd_preview(int index, HWND hwnd);
HWND get_first_window();
void reset_window();
void reset_vars_to_default();
static LRESULT CALLBACK screen_resize_wndproc(
HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
static void wndproc_wm_sizing(int edge, RECT& rect);
};
}
+36 -88
View File
@@ -1,107 +1,55 @@
#undef CINTERFACE
#include "sdvx_sub.h"
#include <fmt/format.h>
#include "games/io.h"
#include "hooks/graphics/backends/d3d9/d3d9_backend.h"
#include "hooks/graphics/backends/d3d9/d3d9_device.h"
#include "util/logging.h"
#include "games/sdvx/sdvx.h"
namespace overlay::windows {
const ImVec4 YELLOW(1.f, 1.f, 0.f, 1.f);
const ImVec4 WHITE(1.f, 1.f, 1.f, 1.f);
SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : GenericSubScreen(overlay) {
this->title = "SDVX Sub Screen";
SDVXSubScreen::SDVXSubScreen(SpiceOverlay *overlay) : Window(overlay), device(overlay->get_device()) {
this->draws_window = false;
this->title = "Sub Screen";
this->toggle_button = games::OverlayButtons::ToggleSubScreen;
const auto padding = ImGui::GetFrameHeight() / 2;
this->texture_size = ImVec2(0, 0);
this->init_size = ImVec2(ImGui::GetIO().DisplaySize.x - (padding * 2), 0.f);
this->init_size.y = (this->init_size.x * 9 / 16) + ImGui::GetFrameHeight();
this->init_pos = ImVec2(ImGui::GetIO().DisplaySize.x / 2 - this->init_size.x / 2, 0);
switch (games::sdvx::OVERLAY_POS) {
case games::sdvx::SDVX_OVERLAY_TOP:
this->init_pos.y = padding;
break;
case games::sdvx::SDVX_OVERLAY_BOTTOM:
this->init_pos.y = ImGui::GetIO().DisplaySize.y - this->init_size.y - padding;
break;
case games::sdvx::SDVX_OVERLAY_MIDDLE:
default:
this->init_pos.y = (ImGui::GetIO().DisplaySize.y / 2) - (this->init_size.y / 2);
break;
}
}
void SDVXSubScreen::build_content() {
this->draw_texture();
/*
if (this->status_message.has_value()) {
ImGui::TextColored(YELLOW, "%s", this->status_message.value().c_str());
} else if (this->texture) {
ImGui::TextColored(WHITE, "Successfully acquired surface texture");
} else {
ImGui::TextColored(YELLOW, "Failed to acquire surface texture");
}
*/
}
bool SDVXSubScreen::build_texture(IDirect3DSurface9 *surface) {
HRESULT hr;
D3DSURFACE_DESC desc {};
hr = surface->GetDesc(&desc);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to get surface descriptor, hr={}", FMT_HRESULT(hr));
return false;
}
hr = this->device->CreateTexture(desc.Width, desc.Height, 0, desc.Usage, desc.Format,
desc.Pool, &this->texture, nullptr);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to create render target, hr={}", FMT_HRESULT(hr));
return false;
}
this->texture_size = ImVec2(1080, 608);
return true;
}
void SDVXSubScreen::draw_texture() {
HRESULT hr;
auto surface = graphics_d3d9_ldj_get_sub_screen();
if (surface == nullptr) {
void SDVXSubScreen::touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) {
if (!this->get_active()) {
return;
}
if (this->texture == nullptr) {
if (!this->build_texture(surface)) {
this->texture = nullptr;
this->texture_size = ImVec2(0, 0);
// SDVX Valkyrie cab needs math to make things work
// what the game expects from touch screen:
// [1080, 0 1920, 1080]
// [0, 0 0, 1920]
surface->Release();
return;
}
}
// actual cursor position given by x_in (on the overlay, normalized):
// [0, 0 1, 0]
// [0, 1 1, 1]
IDirect3DSurface9 *texture_surface = nullptr;
hr = this->texture->GetSurfaceLevel(0, &texture_surface);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to get texture surface, hr={}", FMT_HRESULT(hr));
// Basically, the game operates on rotated coordinates, but on top of that,
// it needs to be adjusted so they match up with the subscreen overlay.
surface->Release();
return;
}
// so the math is:
// Xin(0, 1) => Yout(0, 1920)
// Yin(0, 1) => Xout(1080, 0)
hr = this->device->StretchRect(surface, nullptr, texture_surface, nullptr, D3DTEXF_NONE);
if (FAILED(hr)) {
this->status_message = fmt::format("Failed to copy back buffer contents, hr={}", FMT_HRESULT(hr));
surface->Release();
texture_surface->Release();
return;
}
surface->Release();
texture_surface->Release();
ImGui::GetBackgroundDrawList()->AddImage(
reinterpret_cast<void *>(this->texture),
ImVec2(0, 0),
this->texture_size,
ImVec2(0, 0),
ImVec2(1, 1));
*x_out = ImGui::GetIO().DisplaySize.x * (1.f - xy_in.y);
*y_out = ImGui::GetIO().DisplaySize.y * xy_in.x;
}
}
+4 -13
View File
@@ -4,27 +4,18 @@
#include <optional>
#include <windows.h>
#include <d3d9.h>
#include "overlay/window.h"
#include "overlay/windows/generic_sub.h"
namespace overlay::windows {
class SDVXSubScreen : public Window {
class SDVXSubScreen : public GenericSubScreen {
public:
SDVXSubScreen(SpiceOverlay *overlay);
void build_content() override;
private:
bool build_texture(IDirect3DSurface9 *surface);
void draw_texture();
std::optional<std::string> status_message = std::nullopt;
IDirect3DDevice9 *device = nullptr;
IDirect3DTexture9 *texture = nullptr;
ImVec2 texture_size;
protected:
void touch_transform(const ImVec2 xy_in, LONG *x_out, LONG *y_out) override;
};
}
+5 -1
View File
@@ -21,6 +21,7 @@ namespace rawinput {
// settings
bool NOLEGACY = false;
uint8_t HID_LIGHT_BRIGHTNESS = 100; // 100%
}
rawinput::RawInputManager::RawInputManager() {
@@ -2211,9 +2212,12 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
auto &value_cap = hid->value_output_caps_list[cap_no];
auto &value_state = hid->value_output_states[cap_no];
// adjust output value per "brightness" setting
auto adjusted_value_state = value_state * HID_LIGHT_BRIGHTNESS / 100;
// build value
LONG usage_value = value_cap.LogicalMin +
lroundf((value_cap.LogicalMax - value_cap.LogicalMin) * value_state);
lroundf((value_cap.LogicalMax - value_cap.LogicalMin) * adjusted_value_state);
if (usage_value > value_cap.LogicalMax) {
usage_value = value_cap.LogicalMax;
} else if (usage_value < value_cap.LogicalMin) {
+2
View File
@@ -16,6 +16,8 @@ namespace rawinput {
// settings
extern bool NOLEGACY;
extern uint8_t HID_LIGHT_BRIGHTNESS;
struct DeviceCallback {
void *data;
std::function<void(void*, Device*)> f;
+5 -8
View File
@@ -1,13 +1,10 @@
In respect to all those dying arcade
games. Keep them alive, support the
developers when you can.
In respect to all those dying arcade games. Keep them alive, support the
developers when you can. Sources should be included, have fun!
Sources should be included, have fun!
Want to collaborate, add features, fix
bugs or clean up messy code? Try to
contact us, any help is appreciated! :)
spice2x fork maintainers:
* sp2xdev
Contributors:
SpiceTools original contributors:
* cube
* felix
* dinsfire
+12 -2
View File
@@ -873,7 +873,12 @@ void touch_get_points(std::vector<TouchPoint> &touch_points, bool overlay) {
}
// overlay override
if (!overlay && overlay::OVERLAY && ImGui::GetIO().WantCaptureMouse) {
if (!overlay &&
overlay::OVERLAY &&
overlay::OVERLAY->get_active() &&
!overlay::OVERLAY->can_transform_touch_input() &&
ImGui::GetIO().WantCaptureMouse) {
return;
}
@@ -895,7 +900,12 @@ void touch_get_events(std::vector<TouchEvent> &touch_events, bool overlay) {
std::lock_guard<std::mutex> lock(TOUCH_EVENTS_M);
// overlay override
if (!overlay && overlay::OVERLAY && ImGui::GetIO().WantCaptureMouse) {
if (!overlay &&
overlay::OVERLAY &&
overlay::OVERLAY->get_active() &&
!overlay::OVERLAY->can_transform_touch_input() &&
ImGui::GetIO().WantCaptureMouse) {
TOUCH_EVENTS.reset();
return;
}
+15 -7
View File
@@ -30,16 +30,24 @@ static inline void load_library_fail(const std::string &file_name, bool fatal) {
std::string info_str { fmt::format(
"\n\nPlease check if {} exists and the permissions are fine.\n"
"If the problem still persists, try installing:\n"
"* Microsoft Visual C++ Redistributables (*all* versions, *both* x86/x64)\n"
" Hint: use one of the AIO installers (e.g. https://github.com/abbodi1406/vcredist)\n"
"* DirectX End-User Runtimes (June 2010) \n"
"* Running Windows 10 \"N\"?\n"
" https://www.microsoft.com/en-us/download/details.aspx?id=8109 \n"
"* Microsoft Visual C++ Redistributable Runtimes (*all* versions, x86 *AND* x64)\n"
" https://github.com/abbodi1406/vcredist (recommended All-In-One installer)\n"
" You may need to run the installer *multiple times* and reboot after each install\n"
"* Running Windows 10 \"N\" or \"KN\" Editions?\n"
" Grab: https://www.microsoft.com/en-us/software-download/mediafeaturepack \n"
" Check: https://support.microsoft.com/en-us/help/4562569/media-feature-pack-for-windows-10-n-may-2020 \n"
"* Running Windows 7 \"N\"?\n"
" Grab: https://www.microsoft.com/en-us/download/details.aspx?id=16546\n"
"* Still have problems?\n"
" Find the missing dependency using: http://www.dependencywalker.com/"
"* Running Windows 7 \"N\" or \"KN\" Editions?\n"
" x86: https://web.archive.org/web/20190810145509/https://download.microsoft.com/download/B/9/B/B9BED058-8669-490E-BA61-D502E4E8BEB1/Windows6.1-KB968211-x86-RefreshPkg.msu \n"
" x64: https://web.archive.org/web/20190810145509/https://download.microsoft.com/download/B/9/B/B9BED058-8669-490E-BA61-D502E4E8BEB1/Windows6.1-KB968211-x64-RefreshPkg.msu \n"
"* Still have problems after installing above?\n"
" Ensure you do NOT have multiple copies of the game DLLs\n"
" Ensure the game DLLs are in the correct place, and double check -modules parameter\n"
" Certain games require specific NVIDIA DLLs when running with AMD GPUs\n"
" Find the missing dependency using:\n"
" https://github.com/lucasg/Dependencies (recommended for most) \n"
" http://www.dependencywalker.com/ (for old OS) \n"
, file_name) };
if (fatal) {
log_fatal("libutils", "{}", info_str);
+1 -1
View File
@@ -84,7 +84,7 @@ struct fmt::formatter<fmt_hresult> {
logger::push(LOG_FORMAT("F", module, format_str, ## __VA_ARGS__), logger::Style::RED); \
\
launcher::stop_subsystems(); \
Sleep(2000); \
Sleep(30000); \
launcher::kill(); \
std::terminate(); \
} ((void) 0 )
+2 -2
View File
@@ -1,7 +1,7 @@
#include "resutils.h"
#include "util/utils.h"
const char *resutil::load_file(int name, LPCSTR type, DWORD *size) {
const char *resutil::load_file(int name, DWORD *size, LPCSTR type) {
HMODULE handle = GetModuleHandle(NULL);
HRSRC rc = FindResource(handle, MAKEINTRESOURCE(name), type);
HGLOBAL rcData = LoadResource(handle, rc);
@@ -12,7 +12,7 @@ const char *resutil::load_file(int name, LPCSTR type, DWORD *size) {
std::string resutil::load_file_string(int name, LPCSTR type) {
DWORD size = 0;
auto data = resutil::load_file(name, type, &size);
auto data = resutil::load_file(name, &size, type);
return std::string(data, size);
}
+1 -1
View File
@@ -5,7 +5,7 @@
namespace resutil {
const char* load_file(int name, LPCSTR type, DWORD* size);
const char* load_file(int name, DWORD* size, LPCSTR type=RT_RCDATA);
std::string load_file_string(int name, LPCSTR type=RT_RCDATA);
std::string load_file_string_crlf(int name, LPCSTR type=RT_RCDATA);
}