added default mapping controller port option (resolves #897)

This commit is contained in:
thrust26 2023-08-02 17:03:57 +02:00
parent 55e3874097
commit f7e46338b8
16 changed files with 237 additions and 57 deletions

View File

@ -30,6 +30,8 @@
* Enhanced Kid Vid support to play tape audio.
* Added port selection, used for controller default mapping
* Added missing PlusROM support for E7 bankswitching.
* Acclerated emulation up to ~15% (ARM).

View File

@ -4041,7 +4041,7 @@
<tr><td>Allow all 4 directions ...</td><td>Allow all 4 joystick directions to be pressed simultaneously</td><td>-joyallow4</td></tr>
<tr><td>Use modifier key combos</td><td>Enable using modifier keys in keyboard actions</td><td>-modcombo</td></tr>
<tr><td>Swap Stelladaptor ports</td><td>Swap the order of the detected Stelladaptors/2600-daptors (see <b>Advanced Configuration - <a href="#Adaptor">Stelladaptor/2600-daptor Support</a></b>)</td><td>-saport</td></tr>
<tr><td>Controller Database</td><td>Show all controllers that Stella knows about, with the option to remove them</td><td>&nbsp;</td></tr>
<tr><td>Controller Database</td><td>Show all controllers that Stella knows about and allow assigning them to a default mapping port and removing them</td><td>&nbsp;</td></tr>
<tr><td>Erase EEPROM</td><td>Erase the whole AtariVox/SaveKey flash memory</td><td>&nbsp;</td></tr>
<tr><td>AtariVox serial port</td><td>Described in further detail in <b>Advanced Configuration - <a href="#AtariVox">AtariVox/SaveKey Support</a></b> </td><td>-avoxport</td></tr>
</table>

View File

@ -228,6 +228,17 @@ bool PhysicalJoystickHandler::remove(string_view name)
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PhysicalJoystickHandler::setPort(string_view name, PhysicalJoystick::Port port)
{
const auto it = myDatabase.find(name);
if(it != myDatabase.end() && it->second.joy != nullptr)
{
it->second.joy->setPort(port);
// TODO: update mappings
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PhysicalJoystickHandler::mapStelladaptors(string_view saport, int ID)
{
@ -359,35 +370,42 @@ void PhysicalJoystickHandler::setStickDefaultMapping(
if(j)
{
switch (mode)
switch(mode)
{
case EventMode::kEmulationMode:
{
// A regular joystick defaults to left or right based on the
// stick number being even or odd; 'daptor joysticks request a
// specific port
const bool useLeftMappings =
j->type == PhysicalJoystick::Type::REGULAR ? ((stick % 2) == 0) :
(j->type == PhysicalJoystick::Type::LEFT_STELLADAPTOR ||
j->type == PhysicalJoystick::Type::LEFT_2600DAPTOR);
// A regular joystick defaults to left or right based on
// the defined port or stick number being even or odd;
// 'daptor' joysticks request a specific port
bool useLeftMappings;
if(j->type == PhysicalJoystick::Type::REGULAR)
{
useLeftMappings = j->port == PhysicalJoystick::Port::LEFT
|| (j->port == PhysicalJoystick::Port::AUTO && (stick % 2) == 0);
}
else
useLeftMappings =
j->type == PhysicalJoystick::Type::LEFT_STELLADAPTOR ||
j->type == PhysicalJoystick::Type::LEFT_2600DAPTOR;
if(useLeftMappings)
{
// put all controller events into their own mode's mappings
for (const auto& item : DefaultLeftJoystickMapping)
for(const auto& item : DefaultLeftJoystickMapping)
setDefaultAction(stick, item, event, EventMode::kJoystickMode, updateDefaults);
for (const auto& item : DefaultLeftKeyboardMapping)
for(const auto& item : DefaultLeftKeyboardMapping)
setDefaultAction(stick, item, event, EventMode::kKeyboardMode, updateDefaults);
for (const auto& item : DefaultLeftDrivingMapping)
for(const auto& item : DefaultLeftDrivingMapping)
setDefaultAction(stick, item, event, EventMode::kDrivingMode, updateDefaults);
}
else
{
// put all controller events into their own mode's mappings
for (const auto& item : DefaultRightJoystickMapping)
for(const auto& item : DefaultRightJoystickMapping)
setDefaultAction(stick, item, event, EventMode::kJoystickMode, updateDefaults);
for (const auto& item : DefaultRightKeyboardMapping)
for(const auto& item : DefaultRightKeyboardMapping)
setDefaultAction(stick, item, event, EventMode::kKeyboardMode, updateDefaults);
for (const auto& item : DefaultRightDrivingMapping)
for(const auto& item : DefaultRightDrivingMapping)
setDefaultAction(stick, item, event, EventMode::kDrivingMode, updateDefaults);
}
@ -404,16 +422,16 @@ void PhysicalJoystickHandler::setStickDefaultMapping(
// and 2600-daptors support two players natively.
const int paddlesPerJoystick = (j->type == PhysicalJoystick::Type::REGULAR && !retron77) ? 1 : 2;
if( paddlesPerJoystick == 2 )
if(paddlesPerJoystick == 2)
{
if( useLeftMappings )
if(useLeftMappings)
{
for (const auto& item : DefaultLeftPaddlesMapping)
for(const auto& item : DefaultLeftPaddlesMapping)
setDefaultAction(stick, item, event, EventMode::kPaddlesMode, updateDefaults);
}
else
{
for (const auto& item : DefaultRightPaddlesMapping)
for(const auto& item : DefaultRightPaddlesMapping)
setDefaultAction(stick, item, event, EventMode::kPaddlesMode, updateDefaults);
}
}
@ -430,29 +448,29 @@ void PhysicalJoystickHandler::setStickDefaultMapping(
const bool useLeftPaddleMappings = (stick % 4) < 2;
const bool useAPaddleMappings = (stick % 2) == 0;
if( useLeftPaddleMappings )
if(useLeftPaddleMappings)
{
if( useAPaddleMappings )
if(useAPaddleMappings)
{
for (const auto& item : DefaultLeftAPaddlesMapping)
for(const auto& item : DefaultLeftAPaddlesMapping)
setDefaultAction(stick, item, event, EventMode::kPaddlesMode, updateDefaults);
}
else
{
for (const auto& item : DefaultLeftBPaddlesMapping)
for(const auto& item : DefaultLeftBPaddlesMapping)
setDefaultAction(stick, item, event, EventMode::kPaddlesMode, updateDefaults);
}
}
else
{
if( useAPaddleMappings )
if(useAPaddleMappings)
{
for (const auto& item : DefaultRightAPaddlesMapping)
for(const auto& item : DefaultRightAPaddlesMapping)
setDefaultAction(stick, item, event, EventMode::kPaddlesMode, updateDefaults);
}
else
{
for (const auto& item : DefaultRightBPaddlesMapping)
for(const auto& item : DefaultRightBPaddlesMapping)
setDefaultAction(stick, item, event, EventMode::kPaddlesMode, updateDefaults);
}
}
@ -466,7 +484,7 @@ void PhysicalJoystickHandler::setStickDefaultMapping(
}
case EventMode::kMenuMode:
for (const auto& item : DefaultMenuMapping)
for(const auto& item : DefaultMenuMapping)
setDefaultAction(stick, item, event, EventMode::kMenuMode, updateDefaults);
break;
@ -1071,13 +1089,19 @@ void PhysicalJoystickHandler::handleHatEvent(int stick, int hat, int value)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
VariantList PhysicalJoystickHandler::database() const
PhysicalJoystickHandler::MinStrickInfoList PhysicalJoystickHandler::minStickList() const
{
VariantList db;
for(const auto& [_name, _info]: myDatabase)
VarList::push_back(db, _name, _info.joy ? _info.joy->ID : -1);
MinStrickInfoList list;
return db;
for(const auto& [_name, _info] : myDatabase)
{
MinStrickInfo stick(_name,
_info.joy ? _info.joy->ID : -1,
_info.joy ? _info.joy->port : PhysicalJoystick::Port::AUTO);
list.push_back(stick);
}
return list;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -46,6 +46,18 @@ using PhysicalJoystickPtr = shared_ptr<PhysicalJoystick>;
*/
class PhysicalJoystickHandler
{
public:
struct MinStrickInfo
{
string name;
int ID;
PhysicalJoystick::Port port;
explicit MinStrickInfo(string _name, int _id, PhysicalJoystick::Port _port)
: name{_name}, ID{_id}, port{_port} {}
};
using MinStrickInfoList = std::vector<MinStrickInfo>;
private:
struct StickInfo
{
@ -74,6 +86,7 @@ class PhysicalJoystickHandler
int add(const PhysicalJoystickPtr& stick);
bool remove(int id);
bool remove(string_view name);
void setPort(string_view name, PhysicalJoystick::Port port);
bool mapStelladaptors(string_view saport, int ID = -1);
bool hasStelladaptors() const;
void setDefaultMapping(Event::Type event, EventMode mode);
@ -112,8 +125,8 @@ class PhysicalJoystickHandler
return j->joyMap.get(mode, button, hat, hatDir);
}
/** Returns a list of pairs consisting of joystick name and associated ID. */
VariantList database() const;
/** Returns a list containing minimal controller info (name, ID, port). */
MinStrickInfoList minStickList() const;
void changeDigitalDeadZone(int direction = +1);
void changeAnalogPaddleDeadZone(int direction = +1);

View File

@ -221,7 +221,7 @@ void PhysicalKeyboardHandler::defineControllerMappings(
const Controller::Type type, Controller::Jack port, const Properties& properties)
{
// Determine controller events to use
if(type == Controller::Type::QuadTari)
if(type == Controller::Type::QuadTari)
{
if(port == Controller::Jack::Left)
{
@ -233,7 +233,7 @@ void PhysicalKeyboardHandler::defineControllerMappings(
myRightMode = getMode(properties, PropType::Controller_Right1);
myRight2ndMode = getMode(properties, PropType::Controller_Right2);
}
}
}
else
{
const EventMode mode = getMode(type);
@ -459,7 +459,7 @@ bool PhysicalKeyboardHandler::isDrivingEvent(const Event::Type event)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PhysicalKeyboardHandler::isCommonEvent(const Event::Type event)
{
return !(isJoystickEvent(event) || isPaddleEvent(event)
return !(isJoystickEvent(event) || isPaddleEvent(event)
|| isKeyboardEvent(event) || isDrivingEvent(event));
}

View File

@ -69,6 +69,7 @@ json PhysicalJoystick::getMap() const
json mapping = json::object();
mapping["name"] = name;
mapping["port"] = getName(port);
for (const auto& mode: {
EventMode::kMenuMode, EventMode::kJoystickMode, EventMode::kPaddlesMode, EventMode::kKeyboardMode, EventMode::kDrivingMode, EventMode::kCommonMode
@ -84,14 +85,19 @@ bool PhysicalJoystick::setMap(const json& map)
int i = 0;
for (const auto& entry: map.items()) {
if (entry.key() == "name") continue;
if (entry.key() == "name")
continue;
if(entry.key() == "port")
{
port = getPort(entry.value());
continue;
}
try {
joyMap.loadMapping(entry.value(), eventModeFromJsonName(entry.key()));
} catch (const json::exception&) {
Logger::error("ignoring invalid json mapping for " + entry.key());
}
i++;
}
@ -107,6 +113,34 @@ bool PhysicalJoystick::setMap(const json& map)
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string PhysicalJoystick::getName(const PhysicalJoystick::Port _port) const
{
static constexpr std::array<string_view,
static_cast<int>(PhysicalJoystick::Port::NUM_PORTS)> NAMES =
{
"Auto", "Left", "Right"
};
return string{NAMES[static_cast<int>(_port)]};
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PhysicalJoystick::Port PhysicalJoystick::getPort(string_view portName) const
{
static constexpr std::array<string_view,
static_cast<int>(PhysicalJoystick::Port::NUM_PORTS)> NAMES =
{
"Auto", "Left", "Right"
};
for(int i = 0; i < static_cast<int>(PhysicalJoystick::Port::NUM_PORTS); ++i)
if (BSPF::equalsIgnoreCase(portName, NAMES[i]))
return PhysicalJoystick::Port{i};
return PhysicalJoystick::Port::AUTO;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
json PhysicalJoystick::convertLegacyMapping(string_view mapping, string_view name)
{
@ -167,7 +201,22 @@ void PhysicalJoystick::getValues(string_view list, IntArray& map)
string PhysicalJoystick::about() const
{
ostringstream buf;
buf << "'" << name << "' with: " << numAxes << " axes, " << numButtons << " buttons, "
buf << "'" << name << "' in ";
switch(port)
{
case Port::LEFT:
buf << "left";
break;
case Port::RIGHT:
buf << "right";
break;
default:
buf << "auto";
break;
}
buf << " port with: "
<< numAxes << " axes, " << numButtons << " buttons, "
<< numHats << " hats";
return buf.str();

View File

@ -43,10 +43,18 @@ class PhysicalJoystick
static constexpr char MODE_DELIM = '>'; // must not be '^', '|' or '#'
public:
enum class Port {
AUTO,
LEFT,
RIGHT,
NUM_PORTS
};
PhysicalJoystick() = default;
nlohmann::json getMap() const;
bool setMap(const nlohmann::json& map);
void setPort(const Port _port) { port = _port; }
static nlohmann::json convertLegacyMapping(string_view mapping,
string_view name);
@ -69,6 +77,7 @@ class PhysicalJoystick
Type type{Type::REGULAR};
int ID{-1};
string name{"None"};
Port port{Port::AUTO};
int numAxes{0}, numButtons{0}, numHats{0};
IntArray axisLastValue;
IntArray buttonLast;
@ -79,6 +88,10 @@ class PhysicalJoystick
private:
static void getValues(string_view list, IntArray& map);
// Convert from string to Port type and vice versa
string getName(const Port _port) const;
Port getPort(string_view portName) const;
friend ostream& operator<<(ostream& os, const PhysicalJoystick& s) {
os << " ID: " << s.ID << ", name: " << s.name << ", numaxis: " << s.numAxes
<< ", numbtns: " << s.numButtons << ", numhats: " << s.numHats;

View File

@ -84,7 +84,7 @@ class Event
UIUp, UIDown, UILeft, UIRight, UIHome, UIEnd, UIPgUp, UIPgDown,
UISelect, UINavPrev, UINavNext, UIOK, UICancel, UIPrevDir,
UITabPrev, UITabNext,
UITabPrev, UITabNext, UIReload,
NextMouseControl, ToggleGrabMouse,
MouseAxisXMove, MouseAxisYMove, MouseAxisXValue, MouseAxisYValue,

View File

@ -169,6 +169,9 @@ void EventHandler::addPhysicalJoystick(const PhysicalJoystickPtr& joy)
setActionMappings(EventMode::kEmulationMode);
setActionMappings(EventMode::kMenuMode);
if(myOverlay)
myOverlay->handleEvent(Event::UIReload);
#endif
}
@ -177,6 +180,9 @@ void EventHandler::removePhysicalJoystick(int id)
{
#ifdef JOYSTICK_SUPPORT
myPJoyHandler->remove(id);
if(myOverlay)
myOverlay->handleEvent(Event::UIReload);
#endif
}
@ -2078,6 +2084,15 @@ void EventHandler::removePhysicalJoystickFromDatabase(string_view name)
#endif
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EventHandler::setPhysicalJoystickPortInDatabase(string_view name,
PhysicalJoystick::Port port)
{
#ifdef JOYSTICK_SUPPORT
myPJoyHandler->setPort(name, port);
#endif
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool EventHandler::addKeyMapping(Event::Type event, EventMode mode, StellaKey key, StellaMod mod)
{

View File

@ -333,11 +333,10 @@ class EventHandler
bool hasOverlay() const { return myOverlay != nullptr; }
/**
Return a list of all physical joysticks currently in the internal database
(first part of variant) and its internal ID (second part of variant).
Return a simple list of all physical joysticks currently in the internal database
*/
VariantList physicalJoystickDatabase() const {
return myPJoyHandler->database();
PhysicalJoystickHandler::MinStrickInfoList physicalJoystickList() const {
return myPJoyHandler->minStickList();
}
/**
@ -346,6 +345,12 @@ class EventHandler
*/
void removePhysicalJoystickFromDatabase(string_view name);
/**
Change the port of the physical joystick identified by 'name' in
the joystick database, only if it is not currently active.
*/
void setPhysicalJoystickPortInDatabase(string_view name, PhysicalJoystick::Port port);
/**
Enable/disable text events (distinct from single-key events).
*/

View File

@ -168,6 +168,7 @@ class Dialog : public GuiObject
virtual void handleJoyUp(int stick, int button);
virtual void handleJoyAxis(int stick, JoyAxis axis, JoyDir adir, int button = JOY_CTRL_NONE);
virtual bool handleJoyHat(int stick, int hat, JoyHatDir hdir, int button = JOY_CTRL_NONE);
virtual void handleEvent(Event::Type event) {};
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
virtual Event::Type getJoyAxisEvent(int stick, JoyAxis axis, JoyDir adir, int button);

View File

@ -425,6 +425,18 @@ void DialogContainer::handleJoyHatEvent(int stick, int hat, JoyHatDir hdir, int
activeDialog->handleJoyHat(stick, hat, hdir, button);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DialogContainer::handleEvent(Event::Type event)
{
if(myDialogStack.empty())
return;
// Send the event to the dialog box on the top of the stack
Dialog* activeDialog = myDialogStack.top();
activeDialog->handleEvent(event);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DialogContainer::reset()
{

View File

@ -23,6 +23,7 @@ class OSystem;
class EventHandler;
#include "EventHandlerConstants.hxx"
#include "Event.hxx"
#include "StellaKeys.hxx"
#include "Stack.hxx"
#include "bspf.hxx"
@ -119,6 +120,13 @@ class DialogContainer
*/
void handleJoyHatEvent(int stick, int hat, JoyHatDir hdir, int button);
/**
Handle an arbitray dialog event.
@param event The send event
*/
void handleEvent(Event::Type event);
/**
Tick the dialog and all its widgets.
*/

View File

@ -20,6 +20,7 @@
#include "Widget.hxx"
#include "Font.hxx"
#include "EditTextWidget.hxx"
#include "PopUpWidget.hxx"
#include "StringListWidget.hxx"
#include "Variant.hxx"
#include "JoystickDialog.hxx"
@ -47,12 +48,23 @@ JoystickDialog::JoystickDialog(GuiObject* boss, const GUI::Font& font,
// Joystick ID
ypos = _h - VBORDER - (buttonHeight + lineHeight) / 2;
auto* t = new StaticTextWidget(this, font, xpos, ypos+2, "Controller ID ");
auto* t = new StaticTextWidget(this, font, xpos, ypos, "Controller ID ");
xpos += t->getWidth();
myJoyText = new EditTextWidget(this, font, xpos, ypos,
font.getStringWidth("Unplugged "), font.getLineHeight(), "");
myJoyText = new EditTextWidget(this, font, xpos, ypos - 2,
font.getStringWidth("Unplugged "), lineHeight, "");
myJoyText->setEditable(false);
// Port
VariantList ports;
VarList::push_back(ports, "Auto", static_cast<Int32>(PhysicalJoystick::Port::AUTO));
VarList::push_back(ports, "Left", static_cast<Int32>(PhysicalJoystick::Port::LEFT));
VarList::push_back(ports, "Right", static_cast<Int32>(PhysicalJoystick::Port::RIGHT));
myJoyPort = new PopUpWidget(this, font, myJoyText->getRight() + fontWidth * 2, ypos - 1,
font.getStringWidth("Right"), lineHeight, ports, "Port ", 0, kPortCmd);
myJoyPort->setToolTip("Define default mapping port.");
wid.push_back(myJoyPort);
// Add buttons at bottom
xpos = _w - buttonWidth - HBORDER;
ypos = _h - VBORDER - buttonHeight;
@ -77,10 +89,11 @@ void JoystickDialog::loadConfig()
myJoyIDs.clear();
StringList sticks;
for(const auto& [_name, _id]: instance().eventHandler().physicalJoystickDatabase())
for(const auto& _entry : instance().eventHandler().physicalJoystickList())
{
sticks.push_back(_name);
myJoyIDs.push_back(_id.toInt());
sticks.push_back(_entry.name);
myJoyIDs.push_back(_entry.ID);
myJoyPorts.push_back(static_cast<int>(_entry.port));
}
myJoyList->setList(sticks);
myJoyList->setSelected(0);
@ -91,6 +104,13 @@ void JoystickDialog::loadConfig()
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void JoystickDialog::handleEvent(Event::Type event)
{
if(event == Event::Type::UIReload)
loadConfig();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void JoystickDialog::handleCommand(CommandSender* sender, int cmd, int data, int id)
{
@ -100,6 +120,13 @@ void JoystickDialog::handleCommand(CommandSender* sender, int cmd, int data, int
close();
break;
case kPortCmd:
myJoyPorts[myJoyList->getSelected()] = myJoyPort->getSelected();
instance().eventHandler().setPhysicalJoystickPortInDatabase(
myJoyList->getSelectedString(),
static_cast<PhysicalJoystick::Port>(myJoyPort->getSelected()));
break;
case kRemoveCmd:
instance().eventHandler().removePhysicalJoystickFromDatabase(
myJoyList->getSelectedString());
@ -107,20 +134,24 @@ void JoystickDialog::handleCommand(CommandSender* sender, int cmd, int data, int
break;
case ListWidget::kSelectionChangedCmd:
if(myJoyIDs[data] >= 0)
{
const bool isPlugged = myJoyIDs[data] >= 0;
if(isPlugged)
{
myRemoveBtn->setEnabled(false);
ostringstream buf;
buf << "C" << myJoyIDs[data];
myJoyText->setText(buf.str());
myJoyPort->setSelected(myJoyPorts[data]);
}
else
{
myRemoveBtn->setEnabled(true);
myJoyText->setText("Unplugged");
myJoyPort->setText("");
}
myJoyPort->setEnabled(isPlugged);
myRemoveBtn->setEnabled(!isPlugged);
break;
}
default:
Dialog::handleCommand(sender, cmd, data, id);
break;

View File

@ -22,6 +22,7 @@ class CommandSender;
class GuiObject;
class ButtonWidget;
class EditTextWidgetWidget;
class PopUpWidget;
class StringListWidget;
#include "Dialog.hxx"
@ -43,17 +44,23 @@ class JoystickDialog : public Dialog
private:
void loadConfig() override;
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
void handleEvent(Event::Type event) override;
private:
StringListWidget* myJoyList{nullptr};
EditTextWidget* myJoyText{nullptr};
PopUpWidget* myJoyPort{nullptr};
ButtonWidget* myRemoveBtn{nullptr};
ButtonWidget* myCloseBtn{nullptr};
IntArray myJoyIDs;
IntArray myJoyPorts;
enum { kRemoveCmd = 'JDrm' };
enum {
kRemoveCmd = 'JDrm',
kPortCmd = 'JDpt'
};
private:
// Following constructors and assignment operators not supported

View File

@ -646,7 +646,7 @@
<DataExecutionPrevention>
</DataExecutionPrevention>
<TargetMachine>MachineX64</TargetMachine>
<LinkTimeCodeGeneration>PGUpdate</LinkTimeCodeGeneration>
<LinkTimeCodeGeneration>PGOptimization</LinkTimeCodeGeneration>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release-Sanitize|x64'">