CUzeBox - The new official Uzebox emulator

The Uzebox now have a fully functional emulator! Download and discuss it here.
User avatar
Jubatian
Posts: 1569
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Jubatian »

Artcfox wrote: Sun Sep 17, 2017 5:28 amMy two new BUFFALO 2145487 Classic USB Gamepad controllers arrived, and to my dismay SDL doesn't support them out of the box. ...
Is this really necessary? I mean adding this stuff to the emulator itself, for me it rather seems like it should belong somewhere else (a general configuration somewhere affecting all SDL games).

After some research I found these:

https://wiki.libsdl.org/SDL_GameControl ... oryEnum%29 - by this page of the SDL documentation, for me it seems like game controller mappings should support using axes for game controller buttons. So adding joystick axis motion events shouldn't be necessary, rather an appropriate mapping needs to be provided.

http://www.generalarcade.com/gamepadtool/ I also found this tool, which pinpoints that SDL2 accepts environment variables by which you may provide your controller mapping. Using that you could possibly set up your controller properly for all SDL2 games which should be more appropriate than tweaking individual game sources (later maybe SDL itself might come with better generalized support).
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Artcfox »

Jubatian wrote: Mon Sep 18, 2017 1:50 pm
Artcfox wrote: Sun Sep 17, 2017 5:28 amMy two new BUFFALO 2145487 Classic USB Gamepad controllers arrived, and to my dismay SDL doesn't support them out of the box. ...
Is this really necessary? I mean adding this stuff to the emulator itself, for me it rather seems like it should belong somewhere else (a general configuration somewhere affecting all SDL games).

After some research I found these:

https://wiki.libsdl.org/SDL_GameControl ... oryEnum%29 - by this page of the SDL documentation, for me it seems like game controller mappings should support using axes for game controller buttons. So adding joystick axis motion events shouldn't be necessary, rather an appropriate mapping needs to be provided.

http://www.generalarcade.com/gamepadtool/ I also found this tool, which pinpoints that SDL2 accepts environment variables by which you may provide your controller mapping. Using that you could possibly set up your controller properly for all SDL2 games which should be more appropriate than tweaking individual game sources (later maybe SDL itself might come with better generalized support).
The hardware itself reports that it has 8 buttons and 2 axes. I can indeed map a d-pad direction to an axis, however the API doesn't allow you to differentiate between positive and negative values for that axis, which results in only half of a working d-pad.

I had the behavior explained in this post: http://steamcommunity.com/app/250760/di ... /?l=french

I took a look at the source code of some Android (and desktop) games which use SDL2, and what they do for mappings is allow those mappings to be read in from a file (implementing their own version of SDL_GameControllerAddMappingsFromFile because that function was not present in SDL 2.0.0, it was adding in a later point release) and then call SDL_GameControllerAddMapping in a loop.

The configuration utility that comes with SDL2 does not generate a valid config string for this game pad because the device name has freaking commas in it.

I think reading it in from a file (or giving people the option of hardcoding that file in the case of Emscripten build) would be the best solution.

Also, I realized that gamepads natively supported by SDL2 will always have A and B and X and Y swapped, because the cannonical layout for the button names is the XBox360 layout, not the SNES layout, so I should change my mapping to reflect the XBox360 layout, and then in the code we can flip the constant button names so that we always translate the physical layout of XBox360 to SNES. When we want to read SNES A, we have to read SDL/XBox360 B, and to read SNES B, we have to read SDL/XBox360 A, and the same for X and Y.
User avatar
Jubatian
Posts: 1569
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Jubatian »

Artcfox wrote: Mon Sep 18, 2017 5:04 pmThe hardware itself reports that it has 8 buttons and 2 axes. I can indeed map a d-pad direction to an axis, however the API doesn't allow you to differentiate between positive and negative values for that axis, which results in only half of a working d-pad.

I had the behavior explained in this post: http://steamcommunity.com/app/250760/di ... /?l=french

(...)
This is about the reason why I hate programming for "modern" APIs...

Anyway, I committed a fix, swapped the A-B and X-Y buttons along with adding the capturing of joystick events. I did it differently to get it smaller, and I think the one you provided could exhibit problematic behavior (there are paths in it on which both directions could end up being pressed which might trigger if the physical hardware can switch between directions in one event). I also rather set 16384 as a compare value, so if anyone used a real analog stick for whatever reason it might still work (with digital ones this shouldn't matter at all as they should return only MIN, zero and MAX).

I think controller configs for now are best supplied by environment variables, that's reasonably cross-platform without extra hassle.

A config file is still a barrier away: It needs a cross-platform method to determine a suitable path for such a file which should provide the general behaviour of the emulator which isn't solved yet.
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Artcfox »

Both my code, and your code breaks the d-pad function completely on an XBox 360 controller, which has both a digital d-pad and analog axes. (Though your code makes games very playable using the XBox 360's actual analog joystick, which funnily enough is easier to not accidentally press diagonal directions than its own d-pad!)

Say you are using the d-pad to try to move, but the actual analog stick is hovering around 0 constantly generating events (which is what happens), but since none of those values cross either threshold, it un-sets any button presses that you might have made on the actual d-pad, and since you only get the button down event once for a d-pad button that's an actual d-pad, it doesn't matter that you're still holding it down if the analog axis hits zero at any point after you started holding it down. (So it doesn't matter if you move the code for the analog axes before the code for the digital buttons.)

The only solution seems to be to remember when a d-pad button is being held down, and not allow the analog axis to unset those buttons unless you've seen the d-pad button being released.

Also, I tried setting that environment variable, but SDL is ignoring it. :(

I know you already think supporting this is messy, so you probably won't like my solution (my guess is you'll want to move the static variables to someplace else where they can be properly cleared when necessary), but this fixes the bug and allows everything to work:

Code: Select all

diff --git a/ginput.c b/ginput.c
index b287719..a2eb9d3 100644
--- a/ginput.c
+++ b/ginput.c
@@ -137,7 +137,8 @@ void  ginput_init(void)
 #ifndef USE_SDL1
 
  /* Open game controller or controllers */
-
+ SDL_GameControllerAddMapping("03000000830500006020000010010000,iBuffalo USB 2-axis 8-button Gamepad,platform:Linux,x:b3,a:b1,b:b0,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,");
+ 
  j = 0U;
  for (i = 0U; i < SDL_NumJoysticks(); i++){
   if (SDL_IsGameController(i)){
@@ -271,7 +272,11 @@ void  ginput_sendevent(SDL_Event const* ev)
  }
 
 #ifndef USE_SDL1
-
+ static boole dpad_left[2] = {0};
+ static boole dpad_right[2] = {0};
+ static boole dpad_up[2] = {0};
+ static boole dpad_down[2] = {0};
+ 
  /* Controller input */
 
  if ( ((ev->type) == SDL_CONTROLLERBUTTONDOWN) ||
@@ -283,15 +288,19 @@ void  ginput_sendevent(SDL_Event const* ev)
   switch (ev->cbutton.button){
    case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
     cu_ctr_setsnes_single(player, CU_CTR_SNES_LEFT, press);
+    dpad_left[player] = press;
     break;
    case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
     cu_ctr_setsnes_single(player, CU_CTR_SNES_RIGHT, press);
+    dpad_right[player] = press;
     break;
    case SDL_CONTROLLER_BUTTON_DPAD_UP:
     cu_ctr_setsnes_single(player, CU_CTR_SNES_UP, press);
+    dpad_up[player] = press;
     break;
    case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
     cu_ctr_setsnes_single(player, CU_CTR_SNES_DOWN, press);
+    dpad_down[player] = press;
     break;
    case SDL_CONTROLLER_BUTTON_Y: /* X-Y swapped due to SDL2 layout corresponding to XBox360 */
     cu_ctr_setsnes_single(player, CU_CTR_SNES_X, press);
@@ -337,16 +346,20 @@ void  ginput_sendevent(SDL_Event const* ev)
 
   if ((ev->jaxis.axis) == 0U){ /* X axis: Left and Right */
    press = ((ev->jaxis.value) <= -16384);
-   cu_ctr_setsnes_single(player, CU_CTR_SNES_LEFT, press);
+   if (!dpad_left[player])
+     cu_ctr_setsnes_single(player, CU_CTR_SNES_LEFT, press);
    press = ((ev->jaxis.value) >=  16384);
-   cu_ctr_setsnes_single(player, CU_CTR_SNES_RIGHT, press);
+   if (!dpad_right[player])
+     cu_ctr_setsnes_single(player, CU_CTR_SNES_RIGHT, press);
   }
 
   if ((ev->jaxis.axis) == 1U){ /* Y axis: Up and Down */
    press = ((ev->jaxis.value) <= -16384);
-   cu_ctr_setsnes_single(player, CU_CTR_SNES_UP, press);
+   if (!dpad_up[player])
+     cu_ctr_setsnes_single(player, CU_CTR_SNES_UP, press);
    press = ((ev->jaxis.value) >=  16384);
-   cu_ctr_setsnes_single(player, CU_CTR_SNES_DOWN, press);
+   if (!dpad_down[player])
+     cu_ctr_setsnes_single(player, CU_CTR_SNES_DOWN, press);
   }
 
  }
User avatar
Jubatian
Posts: 1569
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Jubatian »

Artcfox wrote: Mon Sep 18, 2017 8:26 pmBoth my code, and your code breaks the d-pad function completely on an XBox 360 controller, which has both a digital d-pad and analog axes.
I knew this wouldn't work, but I didn't know there were actually any controller which had both digital and analog X & Y movement axes (why would one have, thought I). I will fix it proper.

Could you experiment some more with those environment variables? If you are using a Bash shell and you are just trying to set them, remember that it matters which shell process actually gets the variables (use "export" as appropriate). It should work, at least I could influence SDL by environment variables some time ago when I needed it for a game.

EDIT: I created the fix, now you should be able to use both.
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Artcfox »

Jubatian wrote: Mon Sep 18, 2017 8:31 pm
Artcfox wrote: Mon Sep 18, 2017 8:26 pmBoth my code, and your code breaks the d-pad function completely on an XBox 360 controller, which has both a digital d-pad and analog axes.
I knew this wouldn't work, but I didn't know there were actually any controller which had both digital and analog X & Y movement axes (why would one have, thought I). I will fix it proper.

Could you experiment some more with those environment variables? If you are using a Bash shell and you are just trying to set them, remember that it matters which shell process actually gets the variables (use "export" as appropriate). It should work, at least I could influence SDL by environment variables some time ago when I needed it for a game.

EDIT: I created the fix, now you should be able to use both.
Your fix doesn't work, because you need to add another dimension to your static arrays and use the player variable to keep them separate.

Edit: I tried using export, but the call to

Code: Select all

SDL_GetHint(SDL_HINT_GAMECONTROLLERCONFIG);
still returns NULL. The only way I can get that function to return the hint is if I call

Code: Select all

SDL_SetHint(SDL_HINT_GAMECONTROLLERCONFIG, "03000000830500006020000010010000,2-axis 8-button gamepad,platform:Linux,x:b3,a:b1,b:b0,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,");
before SDL_Init() is called, at which point, we should just use the proper

Code: Select all

SDL_GameControllerAddMapping()
function, which can be called any time.

Here is a fix to your fix:

Code: Select all

diff --git a/ginput.c b/ginput.c
index 9281bf5..db0e4ed 100644
--- a/ginput.c
+++ b/ginput.c
@@ -46,10 +46,10 @@ static SDL_GameController* ginput_gamectr[2] = {NULL, NULL};
 static auint ginput_gamectr_id[2] = {0U, 0U};
 
 /* Directional moves collected from digital inputs */
-static boole ginput_gamectr_ddig[4] = {FALSE, FALSE, FALSE, FALSE};
+static boole ginput_gamectr_ddig[2][4] = {FALSE, FALSE, FALSE, FALSE};
 
 /* Directional moves collected from analog inputs */
-static boole ginput_gamectr_dana[4] = {FALSE, FALSE, FALSE, FALSE};
+static boole ginput_gamectr_dana[2][4] = {FALSE, FALSE, FALSE, FALSE};
 
 /* Controller name when no name string is available */
 static const char ginput_ctr_noname[] = "<no name>";
@@ -143,6 +143,7 @@ void  ginput_init(void)
 #ifndef USE_SDL1
 
  /* Open game controller or controllers */
+ SDL_GameControllerAddMapping("03000000830500006020000010010000,iBuffalo USB 2-axis 8-button Gamepad,platform:Linux,x:b3,a:b1,b:b0,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,");
 
  j = 0U;
  for (i = 0U; i < SDL_NumJoysticks(); i++){
@@ -222,28 +223,28 @@ void  ginput_sendevent(SDL_Event const* ev)
   switch (ev->key.keysym.sym){
    case SDLK_LEFT:
 #ifndef USE_SDL1
-    ginput_gamectr_ddig[0] = press;
+    ginput_gamectr_ddig[0][0] = press;
 #else
     cu_ctr_setsnes_single(player, CU_CTR_SNES_LEFT, press);
 #endif
     break;
    case SDLK_RIGHT:
 #ifndef USE_SDL1
-    ginput_gamectr_ddig[1] = press;
+    ginput_gamectr_ddig[0][1] = press;
 #else
     cu_ctr_setsnes_single(player, CU_CTR_SNES_RIGHT, press);
 #endif
     break;
    case SDLK_UP:
 #ifndef USE_SDL1
-    ginput_gamectr_ddig[2] = press;
+    ginput_gamectr_ddig[0][2] = press;
 #else
     cu_ctr_setsnes_single(player, CU_CTR_SNES_UP, press);
 #endif
     break;
    case SDLK_DOWN:
 #ifndef USE_SDL1
-    ginput_gamectr_ddig[3] = press;
+    ginput_gamectr_ddig[0][3] = press;
 #else
     cu_ctr_setsnes_single(player, CU_CTR_SNES_DOWN, press);
 #endif
@@ -304,16 +305,16 @@ void  ginput_sendevent(SDL_Event const* ev)
 
   switch (ev->cbutton.button){
    case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
-    ginput_gamectr_ddig[0] = press;
+    ginput_gamectr_ddig[player][0] = press;
     break;
    case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
-    ginput_gamectr_ddig[1] = press;
+    ginput_gamectr_ddig[player][1] = press;
     break;
    case SDL_CONTROLLER_BUTTON_DPAD_UP:
-    ginput_gamectr_ddig[2] = press;
+    ginput_gamectr_ddig[player][2] = press;
     break;
    case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
-    ginput_gamectr_ddig[3] = press;
+    ginput_gamectr_ddig[player][3] = press;
     break;
    case SDL_CONTROLLER_BUTTON_Y: /* X-Y swapped due to SDL2 layout corresponding to XBox360 */
     cu_ctr_setsnes_single(player, CU_CTR_SNES_X, press);
@@ -359,24 +360,24 @@ void  ginput_sendevent(SDL_Event const* ev)
 
   if ((ev->jaxis.axis) == 0U){ /* X axis: Left and Right */
    press = ((ev->jaxis.value) <= -16384);
-   ginput_gamectr_dana[0] = press;
+   ginput_gamectr_dana[player][0] = press;
    press = ((ev->jaxis.value) >=  16384);
-   ginput_gamectr_dana[1] = press;
+   ginput_gamectr_dana[player][1] = press;
   }
 
   if ((ev->jaxis.axis) == 1U){ /* Y axis: Up and Down */
    press = ((ev->jaxis.value) <= -16384);
-   ginput_gamectr_dana[2] = press;
+   ginput_gamectr_dana[player][2] = press;
    press = ((ev->jaxis.value) >=  16384);
-   ginput_gamectr_dana[3] = press;
+   ginput_gamectr_dana[player][3] = press;
   }
 
  }
 
- cu_ctr_setsnes_single(player, CU_CTR_SNES_LEFT,  ginput_gamectr_ddig[0] || ginput_gamectr_dana[0]);
- cu_ctr_setsnes_single(player, CU_CTR_SNES_RIGHT, ginput_gamectr_ddig[1] || ginput_gamectr_dana[1]);
- cu_ctr_setsnes_single(player, CU_CTR_SNES_UP,    ginput_gamectr_ddig[2] || ginput_gamectr_dana[2]);
- cu_ctr_setsnes_single(player, CU_CTR_SNES_DOWN,  ginput_gamectr_ddig[3] || ginput_gamectr_dana[3]);
+ cu_ctr_setsnes_single(player, CU_CTR_SNES_LEFT,  ginput_gamectr_ddig[player][0] || ginput_gamectr_dana[player][0]);
+ cu_ctr_setsnes_single(player, CU_CTR_SNES_RIGHT, ginput_gamectr_ddig[player][1] || ginput_gamectr_dana[player][1]);
+ cu_ctr_setsnes_single(player, CU_CTR_SNES_UP,    ginput_gamectr_ddig[player][2] || ginput_gamectr_dana[player][2]);
+ cu_ctr_setsnes_single(player, CU_CTR_SNES_DOWN,  ginput_gamectr_ddig[player][3] || ginput_gamectr_dana[player][3]);
 
 #endif
 
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Artcfox »

I read the source code for SDL, and I was setting the wrong variable name.

Code: Select all

#define SDL_HINT_GAMECONTROLLERCONFIG "SDL_GAMECONTROLLERCONFIG"
Which is why calling the SDL_SetHint() function worked, because it has access to that #define, while the environment does not. Grr!

So now I can get it to recognize my controller by running it like this:

Code: Select all

SDL_GAMECONTROLLERCONFIG='03000000830500006020000010010000,iBuffalo USB 2-axis 8-button Gamepad,platform:Linux,x:b3,a:b1,b:b0,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,' ./cuzebox bugz.uze
but I still don't understand why we don't want this controller to just work out-of-the-box for everyone, especially since you need to download and compile SDL yourself in order to get the utility that can generate that mapping string (it's not even compiled by default if you compile SDL), and then you have to understand why the buttons don't register when you press them in the configuration utility, and then you have to realize that you have to skip pressing the d-pad at all until you get to the analog stick configuration (different from configuring the analog hats!), and finally even if you somehow knew all that and did everything correctly, the configuration string that the utility spits out in the end STILL DOESN'T FREAKING WORK because the manufacturer decided to use a comma in the device name, and instead of generating an error, that just silently causes half of the buttons on the controller to inexplicably not work.

Given that it took me hours of research just to get a working mapping string (I almost returned these controllers because I thought they were broken), can we please just include that line in the code so normal sane people who see that this is the #1 rated USB SNES controller on Amazon.com can just plug it in and have it work automatically?
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Artcfox »

Alternatively, you could just do this:

Code: Select all

diff --git a/ginput.c b/ginput.c
index 2d028f2..6db5eae 100644
--- a/ginput.c
+++ b/ginput.c
@@ -142,6 +142,8 @@ void  ginput_init(void)
 
 #ifndef USE_SDL1
 
+ SDL_GameControllerAddMappingsFromFile("gamecontrollerdb.txt");
+
  for (j = 0U; j < 2U; j++){
   for (i = 0U; i < 4U; i++){
    ginput_gamectr_ddig[j][i] = FALSE;
And then the gamecontrollerdb.txt file from here: https://github.com/gabomdq/SDL_GameControllerDB

will correctly enable this controller, and all of the other community-sourced controller mappings.

Personally I think it would be better to have all of those built-in (right before calling that function) so in case anyone wants to override what's in the built-in database, they can just provide their own gamecontrollerdb.txt file. Thoughts?
User avatar
Jubatian
Posts: 1569
Joined: Thu Oct 01, 2015 9:44 pm
Location: Hungary
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Jubatian »

Artcfox wrote: Mon Sep 18, 2017 9:04 pmYour fix doesn't work, because you need to add another dimension to your static arrays and use the player variable to keep them separate.
Still? I thought I fixed this. Or did you happen to check the code out just before I noticed and committed a proper 2 player solution?
Artcfox wrote: Mon Sep 18, 2017 10:07 pm... so normal sane people who see that this is the #1 rated USB SNES controller on Amazon.com can just plug it in and have it work automatically?
If it works like Google or like for me suggesting stuff by previous history (primarily books), other people might see different things. I am very sceptical about consumer products, in a DB file which can be updated seperately, it is OK, otherwise it is only a clutter in code. If I take for example Debian's update schedule (like if, say, CUzeBox was actually in the Debian repo), the time one such fix propagated to the end user, you could only acquire the particular hardware second-hand if you are lucky.

What about this I found in a previous post?

http://www.generalarcade.com/gamepadtool/

If this works (I see binaries offered, even), I think it would be a lot more appropriate solution since you can use it to easily get any controller working. (The DB file could be okay, too, but I would do some research first. Such as I think if you have Steam it has such a file for SDL2 somewhere, at least by the linked forum posts it seems like so, anyway, it needs some global configuration source which is still a barrier away).
User avatar
Artcfox
Posts: 1382
Joined: Thu Jun 04, 2015 5:35 pm
Contact:

Re: CUzeBox - a new Uzebox emulator in progress

Post by Artcfox »

Jubatian wrote: Tue Sep 19, 2017 5:30 am
Artcfox wrote: Mon Sep 18, 2017 9:04 pmYour fix doesn't work, because you need to add another dimension to your static arrays and use the player variable to keep them separate.
Still? I thought I fixed this. Or did you happen to check the code out just before I noticed and committed a proper 2 player solution?
Artcfox wrote: Mon Sep 18, 2017 10:07 pm... so normal sane people who see that this is the #1 rated USB SNES controller on Amazon.com can just plug it in and have it work automatically?
If it works like Google or like for me suggesting stuff by previous history (primarily books), other people might see different things. I am very sceptical about consumer products, in a DB file which can be updated seperately, it is OK, otherwise it is only a clutter in code. If I take for example Debian's update schedule (like if, say, CUzeBox was actually in the Debian repo), the time one such fix propagated to the end user, you could only acquire the particular hardware second-hand if you are lucky.

What about this I found in a previous post?

http://www.generalarcade.com/gamepadtool/

If this works (I see binaries offered, even), I think it would be a lot more appropriate solution since you can use it to easily get any controller working. (The DB file could be okay, too, but I would do some research first. Such as I think if you have Steam it has such a file for SDL2 somewhere, at least by the linked forum posts it seems like so, anyway, it needs some global configuration source which is still a barrier away).
I checked the code out before you had a proper solution. It works now.

I believe that every game (including the ones on Steam) use that database file. It just uses a different configuration tool to generate the mappings. That gamepadtool config program looks very close to the one that comes with SDL, but with some additional bells and whistles on it. I think we just want everything to work out of the box for people, rather than have to mess with gamepad configurations. That database file even enabled my really old gamepad, so it properly enabled everything that I have in my house w/o having to run any configuration utility.

So let me clarify, I think that we should have it use that file, if you really don't want it to be built-in.
Post Reply