Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
cheetah7071
Oct 20, 2010

honk honk
College Slice
well, I was sort of picturing that the code was a mess of conditionals that were making it ugly and confusing, because otherwise why ask about it here? if the code looks better right now than it would splitting it up, then yeah keep it as it is.

Adbot
ADBOT LOVES YOU

Nalin
Sep 29, 2007

Hair Elf
There are many C++ features that people hem and haw over and like to proclaim that nobody should use them. The person in question is just new to C++ and was concerned if this was one of those features that would make life difficult down the road.

I am of the opinion that using "if constexpr" is a LOT more easier than dealing with SFINAE stuff on templates and to go ahead and use it all you want.

cheetah7071
Oct 20, 2010

honk honk
College Slice
speaking of hemming and hawing over C++ features that people proclaim you shouldn't use, is this pattern reasonable or insane?

I have a singleton class whose job is to translate between user input and the data objects that the code needs to do its job. Sometimes that's simple (the user inputs '5' and the object just returns 5) and sometimes its extremely complex (the user specifies a folder and the object reads all the files in that folder, coheres the information, and returns a complex data object). As far as the code that uses those results are concerned, this is a black box, which is great, but I'm finding it difficult to test the code that uses it. Ideally, I'd like a situation where, in the real code, the functions ask the real black box, and in tests, they ask a spoof of that object. I'm considering using #define to macro the singleton-getter call, and using some preprocessor nonsense to make that define be different in tests.

In a test header, which is included first in the tests:
C++ code:
#define SPOOF
#define SINGLETON Spoofer::getSpoofer()

class Spoofer {
	static Spoofer& getSpoofer();

	void setParam(int x);
	int getParam();

	//the rest of the boilerplate needed to set up the singleton pattern
};
In the actual code:
C++ code:

#ifndef SPOOF
#define SINGLETON Singleton::getSingleton()
#endif

class Singleton {
	//warning! use the SINGLETON macro rather than calling this directly!
	static Singleton& getSingleton();

	int getParam();

	//singleton boilerplate
};
And then I'd just use the SINGLETON macro everywhere I wanted it (while still being able to call Singleton::getSingleton() directly for the tests on that class itself).

I'm a bit leery of it because it requires the main code to understand how the tests are written, creates a function which must never be called, and just feels too clever by half. But maybe this is just a reasonable way to handle it? I dunno.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Are you asking "how do I do dependency injection"?

A quick and dirty way is to have your implementation be defined in a separate translation unit, and then have your tests link against the fake implementation while your production build links in the real one.

I don't think your macro scheme is a great way to do dependency injection, for the reasons you've outlined and a few more. A common alternative would be to use a template parameter to determine which implementation is used - real code will then work with a MyClass<RealDependency>, while your tests can instantiate and test a MyClass<FakeDependency>.

Alternatively you can make all the methods on your real dependency virtual, and use some sort of DI container to determine at runtime which dependency implementation should be used.

cheetah7071
Oct 20, 2010

honk honk
College Slice
So, in the template solution, I'd have a template which essentially boils down to a boolean "is this being run in a real environment, or a test environment" on every class dependent on user parameters?

The translation unit solution is enticing, though it forces me to split my code kind of weirdly--I'd need to put every class which depends on user parameters into a separate translation unit from the class which reads them, and also split the tests on those two translation units as well.

e: I had originally considered and rejected the polymorphism solution because I thought these functions were being called inside big loops and I didn't want the performance hit of the function lookup table, but it looks like I don't actually do that anymore, I must have gotten rid of it all in some previous refactor. It's possible that just passing the source of user parameters (whether real or spoofed) into the constructor of the later objects is the least annoying option

cheetah7071 fucked around with this message at 02:59 on Dec 18, 2022

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

cheetah7071 posted:

So, in the template solution, I'd have a template which essentially boils down to a boolean "is this being run in a real environment, or a test environment" on every class dependent on user parameters?

Not quite - it's "which dependency are we using". You could imagine a unit test that sometimes uses the fake dependency (when it's testing some specific interaction with user parameters) and other times uses the real one, or uses various different fake implementations to test different things, stuff like that.

quote:

The translation unit solution is enticing, though it forces me to split my code kind of weirdly--I'd need to put every class which depends on user parameters into a separate translation unit from the class which reads them, and also split the tests on those two translation units as well.

It doesn't seem particularly weird? Noting that each .cpp file is a separate translation unit, I would have kind of have expected that you'd be doing that already?

Twerk from Home
Jan 17, 2009

This avatar brought to you by the 'save our dead gay forums' foundation.

cheetah7071 posted:

So, in the template solution, I'd have a template which essentially boils down to a boolean "is this being run in a real environment, or a test environment" on every class dependent on user parameters?

If I'm understanding correctly then your real code would use Foo<Singleton> and your test code would use Foo<Spoofer>, and then you just call T::getSingleton() (and update spoofer to have the same signature as your real singleton).

I had not thought about or ever seen templates for compile-time DI, but my code is going to have fewer virtual functions in the future thanks to this.

cheetah7071
Oct 20, 2010

honk honk
College Slice
well I'm a big dummy who thought that "translation unit" meant "all the files that get bundled into a single compilation", which includes multiple source files (and, in my current setup, both the parameter-reading singleton class and the parameter-using classes I'm having trouble testing)

In that case, I don't actually understand the solution. I thought you meant I'd compile the true user-parameter-reader into one static library, compile the spoofed class into another static library, and have different projects link differently--the test on the parameter-using class would link to the spoofed version, and the real executable would link to the unspoofed version. This means separating the parameter-reading and parameter-using cpp files into separate static libraries, which is what I was referring to as being kind of annoying. They're already in separate files, of course.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
Depending on how your build is set up, it can be a little cumbersome to have your tests not include the file for the real implementation in their build. (You also need to exclude the file for your fake implementation from the production build, but that's often not so bad if you can shove it in a separate "testing fakes" directory or something).

If both files end up in the same build then it'll break since you're defining the same symbols multiple times.

AgentF
May 11, 2009
Question: how come this code compiles and works fine with gcc but not msvc?


code:

std::inclusive_scan( input.cbegin(), input.cend(), output.begin(), std::ranges::max );

The error message is: " 'std::ranges::_Max_fn::_Max_fn(const std::ranges::_Max_fn &)': attempting to reference a deleted function". My only guess is that I'm creating an instance of the std::ranges::max struct and then it attempts to call the copy constructor when passing it into the STL function call? I'm not sure how to fix this problem without introducing the noise of a lambda. Neither passing a reference to max nor trying to std::move it work either.

Phobeste
Apr 9, 2006

never, like, count out Touchdown Tom, man

AgentF posted:

Question: how come this code compiles and works fine with gcc but not msvc?


code:
std::inclusive_scan( input.cbegin(), input.cend(), output.begin(), std::ranges::max );

First I was gonna blame msvc until I noted that they added really good ranges support. Then I was gonna blame the docs on ranges::max saying that it doesn't participate in ADL, which msvc might be using under the hood, so I tried using std::max instead of std::ranges::max and there, while it doesn't have the problems with internal generated functions, it still can't deduce the template arguments, so I have no drat idea

Xarn
Jun 26, 2015

cheetah7071 posted:

So, in the template solution, I'd have a template which essentially boils down to a boolean "is this being run in a real environment, or a test environment" on every class dependent on user parameters?

The translation unit solution is enticing, though it forces me to split my code kind of weirdly--I'd need to put every class which depends on user parameters into a separate translation unit from the class which reads them, and also split the tests on those two translation units as well.

e: I had originally considered and rejected the polymorphism solution because I thought these functions were being called inside big loops and I didn't want the performance hit of the function lookup table, but it looks like I don't actually do that anymore, I must have gotten rid of it all in some previous refactor. It's possible that just passing the source of user parameters (whether real or spoofed) into the constructor of the later objects is the least annoying option

No, you hide the singleton implementation into the template. Simple example is lock over a mutex:
C++ code:
template <typename T>
class simple_lock {
    T& m_mut;
public:
    simple_lock(T& mutex):
        m_mut(mutex) {
        m_mut.lock();
    }
    ~simple_lock() {
        m_mut.unlock();
    }
};
If you give this std::mutex it does standard locking. If you give it a std::shared_lock, some test::guarded_mutex type that does, say, deadlock checking, it will still work. simple_lock does not need to know about either of the types, but it uses their behaviour as asked.

Similarly your class can be templated over the data provider and then you instantiate it with real/test provider as needed.


------

OTOH I generally don't recommend using dependency injection via template parameters, because to do so you have to make the whole class a template.

Nalin
Sep 29, 2007

Hair Elf

AgentF posted:

Question: how come this code compiles and works fine with gcc but not msvc?


code:
std::inclusive_scan( input.cbegin(), input.cend(), output.begin(), std::ranges::max );
The error message is: " 'std::ranges::_Max_fn::_Max_fn(const std::ranges::_Max_fn &)': attempting to reference a deleted function". My only guess is that I'm creating an instance of the std::ranges::max struct and then it attempts to call the copy constructor when passing it into the STL function call? I'm not sure how to fix this problem without introducing the noise of a lambda. Neither passing a reference to max nor trying to std::move it work either.

https://godbolt.org/z/e541rovPn

This seems to work. What is weird is that inclusive scan says it takes a "FunctionObject" for "binary_op", of which a function pointer should be valid, but the example uses stuff from <functional>, which are all structs with an operator() override.

Using a struct with an operator() override seemed to have worked.

cheetah7071
Oct 20, 2010

honk honk
College Slice

Xarn posted:

No, you hide the singleton implementation into the template. Simple example is lock over a mutex:
C++ code:
template <typename T>
class simple_lock {
    T& m_mut;
public:
    simple_lock(T& mutex):
        m_mut(mutex) {
        m_mut.lock();
    }
    ~simple_lock() {
        m_mut.unlock();
    }
};
If you give this std::mutex it does standard locking. If you give it a std::shared_lock, some test::guarded_mutex type that does, say, deadlock checking, it will still work. simple_lock does not need to know about either of the types, but it uses their behaviour as asked.

Similarly your class can be templated over the data provider and then you instantiate it with real/test provider as needed.


------

OTOH I generally don't recommend using dependency injection via template parameters, because to do so you have to make the whole class a template.

Yeah I did understand the implementation but was obviously too flippant in how I described it since two people thought I literally meant a boolean. I was just unhappy with the idea of having a template which would only ever take one value in the main production, and would only ever take a second value in testing (hence, feeling like a boolean, but not literally being a boolean). Though as Jabor pointed out, I wasn't quite right there, because it might be helpful to have multiple test implementations.

I don't really want to do that because unless there's some trick I'm missing, I need to either make all the functions inline or put the spoofer class in a place visible to the real code, instead of with the test code, neither of which I'm super a fan of. I'd rather use polymorphism and pass a pointer to the implementation in the constructor, I think

cheetah7071 fucked around with this message at 18:15 on Dec 18, 2022

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

Nalin posted:

https://godbolt.org/z/e541rovPn

This seems to work. What is weird is that inclusive scan says it takes a "FunctionObject" for "binary_op", of which a function pointer should be valid, but the example uses stuff from <functional>, which are all structs with an operator() override.

Using a struct with an operator() override seemed to have worked.

std::ranges::max is not a function, it's one of those weird "niebloid" things in the ranges library. Maybe MSVC forbids copying those, but I couldn't find any information if that would be a bug or not.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem

cheetah7071 posted:

I don't really want to do that because unless there's some trick I'm missing, I need to either make all the functions inline or put the spoofer class in a place visible to the real code, instead of with the test code, neither of which I'm super a fan of. I'd rather use polymorphism and pass a pointer to the implementation in the constructor, I think

Your TestDependency only needs to be visible at the point you instantiate the MyClass<TestDependency> template - it doesn't need to be visible from where the MyClass template is defined or from any of the production code that uses a MyClass<RealDependency>.

cheetah7071
Oct 20, 2010

honk honk
College Slice

Jabor posted:

Your TestDependency only needs to be visible at the point you instantiate the MyClass<TestDependency> template - it doesn't need to be visible from where the MyClass template is defined or from any of the production code that uses a MyClass<RealDependency>.

But if I do it that way, I have to make the MyClass header-only and inline, right? That's what I meant by inline or the class has to be visible (so you can declare in the cpp that you want the compiler to define that instantiation)

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe
That is overwhelmingly how people write templates. It is possible to write templates in implementation files by painstakingly adding explicit instantiations, but that is very uncommon.

cheetah7071
Oct 20, 2010

honk honk
College Slice

rjmccall posted:

That is overwhelmingly how people write templates. It is possible to write templates in implementation files by painstakingly adding explicit instantiations, but that is very uncommon.

yeah I know, I was just saying that that was why I preferred the other option, in this case, since the set of relevant templates is so restricted, rather than needing to be very broad

Foxfire_
Nov 8, 2010

When I template something solely for the sake of dependency injection and don't want to make things virtual instead, I usually do:

A) A header file with just the template declaration
B) A header file that #includes (A) and has nothing except a typedef for the class against its non-test types
C) A header file that #includes (A) and has the template implementations
D) A source file that #includes (B) and (C) and has an explicit instantiation of the template with non-test types

Test code #includes (C) and instantiates with its test doubles. Normal client code #includes (B) and uses the typedef. (D) is its own TU compiled separately from everything else
It keeps the implementation out of every TU and cuts down on name explosions in client code when one of the real dependencies is itself a template.

SwissArmyDruid
Feb 14, 2014

by sebmojo
Hey goons. This is gonna start out sounding incredibly paranoid. Whether or not it remains looking incredibly paranoid by the end, we'll see.

There's a ReShade fork out there called GShade. (Reshade is written in C++, so that's why I'm starting here.) It lauds itself as the superior option over everything else. ReShade is open under a BSD license, but GShade claims some parts are closed-source.

GShade does a weird thing: It has a version check functionality. So when it hooks into whatever game you may be playing, it is dialling up duckduckgo to check for a connection, and then dialling up github to check for the latest version, and if it finds a new version, shuts itself off.

It's a loving post process injector. Fine, sure, I'm sure there's nothing inherently malicious about a version check, but even after having been asked for multiple years now, for an option for it to not disable itself upon finding a new version, or explaining the reasoning behind it, the dev(s) have been incredibly recalcitrant and closed-lipped. Again, for years now. The last time I thought about this was about four years ago, before the cryptoboom, COVID, and silicon shortage, and my 1070 blew up, and I couldn't play games while running Reshade anymore. I only remembered about it again because someone I share a discord with asked if their tone had been overly hostile when feature requesting a "do not check version" toggle like I had several years earlier, only to get the exact same snippy response with zero explanation.

The behaviour itself is not unlike devs and maintainers of other code projects. But the combination of closed-source, and an unwillingness to answer questions over an extended period of time... makes ME suspicious, and I wonder why aren't OTHER people also suspicious.

Can anyone point me in the direction of the thread I should be asking this? This is way outside of my wheelhouse and based entirely on nothing but paranoia.

edit: right, the question, sorry. Does this strike anyone else as weird, and maybe have the skills and/or tools to examine it for the greater good?

SwissArmyDruid fucked around with this message at 06:45 on Dec 25, 2022

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
I think you forgot to actually ask the question?

Absurd Alhazred
Mar 27, 2010

by Athanatos
Infosec Thread maybe? Although it seems like a lovely product fundamentally, what product just bricks itself when there's a new version instead of telling you a new one is available and allowing you to download it?

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

it disables itself when the game it’s being injected into gets an update, until they can validate that it still works properly and avoid a widespread reputation that they crash games

I am certain that they have answered that question many times over the years and are sick of doing so for each person who thinks they’re the first to (come up with a clever way to) ask. it’s the fate of every remotely successful project

just because you have a question about someone’s work doesn’t mean that you’re entitled to a response at all, let alone one that you might personally find compelling, whatever their software license might be

SwissArmyDruid
Feb 14, 2014

by sebmojo

Absurd Alhazred posted:

Infosec Thread maybe? Although it seems like a lovely product fundamentally, what product just bricks itself when there's a new version instead of telling you a new one is available and allowing you to download it?

I mean, every console since the original Xbox? =P

But again, this is just a post-process injector. There aren't, or shouldn't be, any manner of functionality that pose any kind of risk or security exploit such that warrants "NO, YOU CANNOT HAVE INCREASED COLOR SATURATION OR GAUSSIAN BLUR BECAUSE THERE IS A NEW VERSION. GO DOWNLOAD IT."

I dunno. I got worries there's a bitcoin miner or DDOS botnet functionality or something, because of how secretive and hostile the dev(s) are.

Subjunctive posted:

it disables itself when the game it’s being injected into gets an update, until they can validate that it still works properly and avoid a widespread reputation that they crash games

I am certain that they have answered that question many times over the years and are sick of doing so for each person who thinks they’re the first to (come up with a clever way to) ask. it’s the fate of every remotely successful project

just because you have a question about someone’s work doesn’t mean that you’re entitled to a response at all, let alone one that you might personally find compelling, whatever their software license might be

You'd think that they'd put it in a FAQ somewhere.

Absurd Alhazred
Mar 27, 2010

by Athanatos
Oh, so you feel entitled to an FAQ now? :smaug:

Ralith
Jan 12, 2011

I see a ship in the harbor
I can and shall obey
But if it wasn't for your misfortune
I'd be a heavenly person today
Literally last week someone complained that a project of mine was crashing on startup, and it was because they had an old version of ReShade that was buggy and breaking everything.

RPATDO_LAMD
Mar 22, 2013

🐘🪠🍆

Subjunctive posted:

it disables itself when the game it’s being injected into gets an update, until they can validate that it still works properly and avoid a widespread reputation that they crash games

I am certain that they have answered that question many times over the years and are sick of doing so for each person who thinks they’re the first to (come up with a clever way to) ask. it’s the fate of every remotely successful project

just because you have a question about someone’s work doesn’t mean that you’re entitled to a response at all, let alone one that you might personally find compelling, whatever their software license might be

That seems like the sort of thing that would be completely solved just by having a half-decent explanatory error message. Like instead of "gshade is out of date, shutting down", it ought to say "this version of the game is new and gshade is not verified to be compatible/safe with it yet. Please update gshade to get the latest compatibility updates"

but if that really is the reason it seems pretty dumb to do runtime telemetry for that instead of cross-referencing a database of compatible stuff on disk or whatever.

Volguus
Mar 3, 2009
I never heard of ReShade and GShade and from their websites it's not clear to me what do these programs do and why would one use them.

quote:

Imagine your favorite game with ambient occlusion, real depth of field effects, color correction and more ... ReShade exposes an automated and generic way to access both frame color and depth information (latter is automatically disabled during multiplayer to prevent exploitation) and all the tools to make it happen.

What does one do with this information? Make the games look better?

Xerophyte
Mar 17, 2008

This space intentionally left blank

Volguus posted:

What does one do with this information? Make the games look better?

For some definition of better, sure. Basically they inject shader post-processing passes into rendering pipelines that do not have them natively. Common uses are color correction, injecting things like screen space occlusion and depth of field effects, bloom, etc.

Here's a mostly reasonable youtube video of someone going through how their gshade preset for FF14 works:
https://www.youtube.com/watch?v=jJnwzkBre8Y
I don't think everything he does makes sense (indiscriminate fullscreen sharpness filters, eugh) but it's more thought out than most.

I'd say that a good 90% of the presets people use look like arse with a ton of awful posterization problems since you fundamentally cannot monotonically map 8 bit data to 8 bit data without crushing details somewhere, but milder color correction effects can still look good and there are a few really nice bokeh filters and similar non-local effects that are slow but good for photo modes.

The gshade person sounds like they may be an rear end in a top hat, but I suspect they're more an rear end in a top hat who is very tired at getting constant bug reports about issues they fixed fice releases ago than an rear end in a top hat who is surreptitiously mining bitcoins in an application that people are constantly profiling. Refusing to let users run outdated software makes life easier for everyone involved as long as you make updating sufficiently painless for the user, and I guess 1 out of 2 is good enough for them.

rjmccall
Sep 7, 2007

no worries friend
Fun Shoe

Volguus posted:

I never heard of ReShade and GShade and from their websites it's not clear to me what do these programs do and why would one use them.

What does one do with this information? Make the games look better?

That seems to be the idea, yeah. You can inject different shaders, textures, or whatever into a game. As far as I can tell, people don’t use it to cheat, and developers seem to be generally okay with it.

more falafel please
Feb 26, 2005

forums poster

Whatever you do is likely to add at least a frame of input lag, fyi.

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!

rjmccall posted:

That seems to be the idea, yeah. You can inject different shaders, textures, or whatever into a game. As far as I can tell, people don’t use it to cheat, and developers seem to be generally okay with it.

That seems like something a dev like whoever does FFXIV would crack down super hard on. If you can inject textures into your game then you can inject bright puke green colored arrows pointing at things like fishing nodes telling you where it is from further away.

RPATDO_LAMD
Mar 22, 2013

🐘🪠🍆
that's not how postprocessing filters work at all
it just sees pixels and polygons and normal vectors and poo poo, it's not reading memory to access all game data

RPATDO_LAMD fucked around with this message at 19:41 on Dec 25, 2022

cheetah7071
Oct 20, 2010

honk honk
College Slice
You'd need more than just graphical information to do that.

There are legit cheat tools like what you describe for ffxiv tmbut they aren't gshade

pseudorandom name
May 6, 2007

Volguus posted:

I never heard of ReShade and GShade and from their websites it's not clear to me what do these programs do and why would one use them.

What does one do with this information? Make the games look better?

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
The closest thing to a "cheat" I've seen done with reshade is for games where you get some sort of title or achievement for map exploration, and it's calculated as "percentage of the map you've come close enough to reveal", it highlights in bright pink the parts of the map that you haven't explored. Helps you figure out which walls you need to scrape along to get that last few percent.

If there's some sort of attack indicator that's visually unique but can get lost in the noise in a tricky fight, you might be able to use it to make those more visually prominent too.

Boris Galerkin
Dec 17, 2011

I don't understand why I can't harass people online. Seriously, somebody please explain why I shouldn't be allowed to stalk others on social media!

Jabor posted:

The closest thing to a "cheat" I've seen done with reshade is for games where you get some sort of title or achievement for map exploration, and it's calculated as "percentage of the map you've come close enough to reveal", it highlights in bright pink the parts of the map that you haven't explored. Helps you figure out which walls you need to scrape along to get that last few percent.

If there's some sort of attack indicator that's visually unique but can get lost in the noise in a tricky fight, you might be able to use it to make those more visually prominent too.

But I was told itt that this isn’t possible

RPATDO_LAMD posted:

that's not how postprocessing filters work at all
it just sees pixels and polygons and normal vectors and poo poo, it's not reading memory to access all game data

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

Yeah there are other programs that will give you cues for FFXIV boss phases or whatever, but I don’t know of a way to do it with ReShade/GShade. I’m surprised that it can do the map colouring, tbh. I guess if a boss has unique geometry and shader you could pattern-match and replace them with something more visible. I haven’t seen that option via GShade, but I haven’t looked that much. (I unapologetically use ACT plugins for raid cues, though, because I don’t want my teammates to suffer for my bad memory.)

Now I sort of want to look at the ReShade Internals too. This forum is really bad for me.

Adbot
ADBOT LOVES YOU

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
The map completion one I remember was for Guild Wars, and AFAIK just did a per-pixel diff of your map screen against the complete fully-explored map and highlighted the differences. Technically it doesn't do anything you couldn't do with the print screen key and a comparison screenshot from the wiki.

The one for boss tells I'm just spitballing, I don't actually know if that's possible.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply