MAMEWorld >> Programming
View all threads Index   Threaded Mode Threaded  

Pages: 1

Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


libmame patch discussion please
#266897 - 10/20/11 09:33 PM


Hello. I am tempted to put this topic in the MAME Chat forum because I feel like topics get much more discussion there, but I'll be good and try to keep this on the relevant board.

As MAME devs will know, I submitted a patch a few days ago to mamedev via the patch submission email for libmame. You can read about libmame at:

http://www.ischo.com/libmame

I would like to get feedback on my patch and the general ideas and implementation thereof. I realize that I cannot expect my patch to be accepted within just a few days of being sent, so please don't take this as a demand for immediate action. I just realize that my patch is almost 100% guaranteed to not be acceptable as it is for a variety of reasons and I want to have a forum for getting the inevitable feedback, and I hope this thread can serve as that.

I believe that the biggest problem with my patch is going to be philosophical: the MAME team probably does not see the point of it, or even if it does see the point of it, may specifically not want to allow the MAME code to be used in this way.

With that in mind, can we talk about the benefits of allowing MAME to be used as a pure emulator, without any of the UI on top of the emulator, via a stable and well documented API? And can we talk about whether or not my API could be better?

Let me start off with re-making some of the points that I made in my patch submission:


Allowing MAME to be compiled as a library with a simple but complete API means that it can be easily used (for both good and bad) in ways that it cannot be easily used now:

  • Embedded directly in a UI wrapper program ("front-end") that has complete control over the running of, display of, and input to, the emulated game
  • The UI menus that control the game can be implemented using whatever visual presentation style a front-end builder wants to implement, not relying on the simple (but effective!) MAME UI
  • MAME can be embedded in places that it cannot easily be embedded now; I can't imagine every way in which this could be useful but there must be something
  • A much cleaner mechanism for add-on features, that doesn't require ANY changes to the core MAME code; for example:
    - client/server play mechanisms
    - rewind/ffwd/resume from prior point of games in progress (for best support of this I would like to alter the libmame API to allow the save/load callback for game states to be passed in instead of always being file based; so the library user could, e.g. store game states in memory instead of on disk)
    - AI mechanisms for playing games automatically


Now I know that MAME is inteded to just be a 'documentation' of arcade history and so such esoteric features are not/should not be a part of MAME itself. That is exactly why making the MAME engine be nothing more than the low level emulation engine, and having all of these other components be 'add-ons' in separate projects, makes so much sense. In fact I would go so far as to say that MAME itself should be nothing more than libmame, to satisfy the purposes of documenting games via emulation; and the end-user product of being able to play those games and add all of these bells and whistles should be a separate project.

It is in a way kind of counter to the entire purpose of MAME, as it is claimed by the MAME team, to have the UI that exists in MAME currently in there; and the video recording capability, and lots of the other end-user functionality. Of course we could argue that all of these things are essential because they allow for a reasonable development platform - features needed for bug feedback and such - but there certainly is alot of grey area there where MAME features are not clearly just for the benefit of developers.

libmame would allow for the true goal of MAME to be very clearly delineated; everything on the 'inside' of libmame would be solely there for the purpose of documenting games via emulation, and everything on the 'outside' would be end user features (which may also be necessary/useful for developers).

OK, now onto the specific design of my libmame patch:


  • It espouses the philosophy of 'POSIX is the portable interface'; systems that don't support POSIX calls (i.e. Windows) have crutches and shims in place in the code to make them look like they do, at least as much as is necessary to support the POSIX calls used. Surely this must rub some MAME developers the wrong way.
  • It is structured as an OSD layer called 'posix' that implements as much of the OSD interface using POSIX calls as possible, and leaves the remainder up to a different layer to provide. This essentially turns the whole "OSD" concept on its head, saying that instead of an OSD, we should just assume POSIX and make that work. I imagine that the MAME team may not like this way of doing things.
  • The libmame API specifically bypasses all of the internal input setup logic of MAME. Instead I espouse a philosophy of collecting every possible controller input into one giant structure, and expecting the program using libmame to provide input via this structure. The program must identify which actual controller input is relevant to the game at hand (there are libmame calls for querying this information), and do its own thing in terms of mapping user input, via whatever mechanism it wants to do that, to game input. This throws away a good deal of MAME's built in technology for doing this. I imagine the MAME team must not like this concept since it discards MAME's approach. But I make no apologies I think this is the right way; different programs built on top of MAME may want to handle user input very differently, and MAME takes some options away from them that libmame gives back.
  • Is there anything obvious that libmame is lacking in terms of access to MAME emulation mechanisms? 'Adjusters' is one that I can think of. Access to memory maps and stuff of emulated games is another (for, e.g. cheat systems). Access to the debugger functionality is a third. I would be happy to incorporate such features and lean on as much existing MAME functionality as possible.
  • Given that I understood the whole MAME internal input device system just well enough to implement libmame, it is possible - actually, very likely - that there are hidden problems with the way that I have done things, especially in the input setup stuff in libmame_rungame.c. I kind of abuse the MAME input mechanism pretty badly, generating virtual input 'devices' (keyboards, mice, light guns) as needed for each game and then hooking those up in a way that libmame then controls them as needed. Are there pitfalls to this that were not obvious to me - things I don't understand well, like analog joystick dead zones, weird device ports that work in weird ways that I might not be covering properly with libmame's input system, anything else?


How does the MAME team feel about the fact that libmame is an 'enabling' technology for MAME license breakers - in that it makes it easier to re-use the MAME emulator in a productized form? I personally feel that you can't stop these people from abusing MAME in this way, and that their job in doing so is already so easy that my additions do not make it materially easier; but MAME devs may disagree. I would love to hear opinions on this.

Finally, would the MAME team be interested in breaking the MAME functionality up in the way that libmame suggests? I.e. moving everything that is emulation onto the inside of libmame and moving/re-implementing everything that is not to a layer or component on the outside? I believe that doing so would make the MAME codebase more amenable to innovation. How do the MAME devs feel about it?

Well even if the libmame patch is not accepted into MAME I intend to maintain it as an external patch so it's going to be out there. It would certainly make my life easier if it were integrated into MAME so that the devs had to make sure not to break it instead of me having to fix it on every MAME release (Aaron Giles is very adept at breaking libmame with his C++-ification efforts! But that being said, the external API of libmame has not changed even over all of those C++-ification changes in the past 3 or 4 MAME releases, so programs using the libmame API have been completely isolated and protected from these MAME internal changes, which I think proves the value of the libmame approach), but I do not expect or demand that the MAME team shares my viewpoints and will not hold a grudge if they don't want my patch. I just want to have reasonable discussion and to be given an opportunity to make my case. Thank you!



R. Belmont
Cuckoo for IGAvania
Reged: 09/21/03
Posts: 9713
Loc: ECV-197 The Orville
Send PM


Re: libmame patch discussion please new [Re: Bryan Ischo]
#267215 - 10/25/11 05:04 PM


> It espouses the philosophy of 'POSIX is the portable interface'; systems that don't
> support POSIX calls (i.e. Windows) have crutches and shims in place in the code to
> make them look like they do, at least as much as is necessary to support the POSIX
> calls used. Surely this must rub some MAME developers the wrong way.

I'm the Mac/Linux/BSD porter guy and *I* think that's wrong.

1) POSIX translation layers on Windows have a long and storied history of failure. Bloat, bad performance, and general bugginess are the usual suspects (Cygwin embodies the trifecta). Even osd/sdl on Windows uses primarily native Windows APIs aside from the SDL audio/video stuff.

2) MAME's existing OSD layer has primitives for anything you should ever need to do in the existing OSD layers (file I/O, graphics, audio, threading, synchronization, and soon sockets), so why ignore them? This is the thing about libmame that I find technically most off-putting; you can get very good portability for free if you simply use the existing OSD layer instead of trying to bolt on your own.



Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


Re: libmame patch discussion please new [Re: R. Belmont]
#267228 - 10/25/11 06:58 PM


> > It espouses the philosophy of 'POSIX is the portable interface'; systems that don't
> > support POSIX calls (i.e. Windows) have crutches and shims in place in the code to
> > make them look like they do, at least as much as is necessary to support the POSIX
> > calls used. Surely this must rub some MAME developers the wrong way.
>
> I'm the Mac/Linux/BSD porter guy and *I* think that's wrong.
>
> 1) POSIX translation layers on Windows have a long and storied history of failure.
> Bloat, bad performance, and general bugginess are the usual suspects (Cygwin embodies
> the trifecta). Even osd/sdl on Windows uses primarily native Windows APIs aside from
> the SDL audio/video stuff.

As it turns out there are very, very few POSIX APIs needed by most non-GUI software, and by MAME In particular, that are not available on Windows. The major one is POSIX threads; but pthreads is available for Windows with the pthreads-win32 library (that works on 64 bit systems now too), and it's even included "for free" with the MAME version of the MingW compiler. I'll admit that I haven't performance tested pthreads-win32 but I had assumed it was pretty good.

Aside from that, the only differences between windows and non-windows in the "posix" OSD is:

- Windows needs to call mkdir(dir) instead of mkdir(dir, 0777)

- Windows needs to call VirtualAlloc/VirtualFree instead of mmap/munmap (and this functionality is only needed by the dynamic recompiler cache and I don't know if that is actually used by MAME proper, I think it might be a MESS thing)

- I use GetCurrentThreadId() as a convenience in the osd_work_queue.c implementation because pthreads-win32 doesn't have integer thread ids; but I don't really even need to do that

So since 99.99% of the code can be identical between all systems, it seems to make sense to me to have it be so instead of having duplicate OSD implementations.

The parts of the OSD that truly cannot be implemented portably is the stuff relating to graphics, sound, and input. But libmame doesn't even want the OSD to provide those; it expects code external to the library to provide those.

And, for what it's worth, I have benchmarked my osd_work_queue implementation fairly thoroughly and it is approximately 3x faster (and uses considerably less code) than the SDL OSD implementation (at least on Linux - maybe it's worse on Windows, I should investigate that).

> 2) MAME's existing OSD layer has primitives for anything you should ever need to do
> in the existing OSD layers (file I/O, graphics, audio, threading, synchronization,
> and soon sockets), so why ignore them? This is the thing about libmame that I find
> technically most off-putting; you can get very good portability for free if you
> simply use the existing OSD layer instead of trying to bolt on your own.

I just like the conciseness of an OSD implementation that isn't duplicated across platforms unnecessarily. With about 5 lines of #ifdef WINDOWS code across the entire POSIX OSD, it is completely portable between Linux and Windows. I haven't tried Mac OS X but since it's even closer to true POSIX than Windows, I would expect to have to make *zero* modifications to the posix OSD.

Also, the sockets API is already very, very portable across platforms so I expect that when MAME supports sockets, the "posix" OSD implementation will not need anything windows specific.

And - the phrase "bolt on your own" is a little bit unfair. It was carefully constructed, and you could replace BOTH of the sdl and windows OSD implementations (except for the graphics, sound, and input part of course!) with the posix implementation and have 1/2 of the code and have it run faster (at least for anything threaded) AND not have to maintain and debug/test two separate OSD implementations.

But if you are saying that in order to have libmame accepted by the MAME team I'd have to back out my OSD implementation and re-use the existing ones, then I would do that. The patch would have to include changes to windows and sdl OSD code instead of adding the posix OSD, but I can do it. Just give the word and it will be done.



Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


Re: libmame patch discussion please new [Re: R. Belmont]
#267247 - 10/26/11 12:04 AM


> This is the thing about libmame that I find
> technically most off-putting

This implies that there are other technically off-putting aspects to my patch. Details, please. This is exactly the kind of feedback I am looking for. Thanks.



Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


Re: libmame patch discussion please new [Re: R. Belmont]
#267912 - 11/03/11 08:01 PM



OK so as I expected, interest in my patch is low, so low that nobody even wants to comment on it.

Message received; libmame will remain an external patch for MAME and should I ever do anything useful based on libmame, anyone who wants to participate will just have to patch their MAME first using the libmame patch.

I wonder how much longer Aaron Gile's C++-ification project is going to last, because keeping up with that is the only painful part of maintaining an external patch.



Lord Nightmare
Speech Synth Berzerker
Reged: 03/08/04
Posts: 855
Loc: PA, USA
Send PM


Re: libmame patch discussion please new [Re: Bryan Ischo]
#268093 - 11/06/11 06:53 AM


> OK so as I expected, interest in my patch is low, so low that nobody even wants to
> comment on it.

I think its just that fairly few of the active devs read this forum.
I could be wrong, though.

LN



"When life gives you zombies... *CHA-CHIK!* ...you make zombie-ade!"



Robbbert
Sir
Reged: 08/21/04
Posts: 3186
Loc: A long way from you
Send PM


Re: libmame patch discussion please new [Re: Lord Nightmare]
#268097 - 11/06/11 08:18 AM


> > OK so as I expected, interest in my patch is low, so low that nobody even wants to
> > comment on it.
>
> I think its just that fairly few of the active devs read this forum.
> I could be wrong, though.
>
> LN

I think you're right; once or twice I asked for help here and got nowhere.

As for this project, although the page doesn't say so, I'm guessing it will be a DLL plus a 'front-end' portion?

As some may remember, MESS was exactly like that and it caused all sorts of issues. When it was merged into one executable, those problems went away. So, Bryan, despite all the effort you've obviously put into this, I kinda doubt it would be accepted (once bitten, twice shy sort of thing). My personal view is that I don't care how the internals are put together as long as it works.



lharms
MAME Fan
Reged: 01/07/06
Posts: 908
Send PM


Re: libmame patch discussion please new [Re: Robbbert]
#268133 - 11/07/11 06:24 AM


> As some may remember, MESS was exactly like that and it caused all sorts of issues.
> When it was merged into one executable, those problems went away. So, Bryan, despite
> all the effort you've obviously put into this, I kinda doubt it would be accepted
> (once bitten, twice shy sort of thing). My personal view is that I don't care how the
> internals are put together as long as it works.

I am sort of interested. What sort of issues? As I dont really follow MESS too closely.

I have usually found a good clean interface does pretty good things (I have also seen it abused). Sounds like in this case even if .lib/.dll doesnt happen he may have found a way to make a nice abstraction that helps? The speedup that was talked about and the ability to port easier.

Pulling it out into a .lib/.dll I have found over the years doesnt really help *UNLESS* the code is really shared between executables. It doesnt gain you much. But underneath the abstraction usually does help. Even if you dont really do the abstraction physically.

There can be reasons to do an external .dll/.lib but I am not sure that would help in this case. As mostly MAME/MAMEUI/others are distributed as 1 .exe each. Typically people run one at a time. The ability to switch out the 'heart' of the program is not really useful just due to the way things are distributed.

If it were me I would leave the .lib/.dll idea alone and concentrate more on the proper abstraction in the code.

I would be semi interested in the thinking behind cleaving it into two parts. Maybe I am seeing it wrong?



Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


Re: libmame patch discussion please new [Re: Lord Nightmare]
#268140 - 11/07/11 09:01 AM


> > OK so as I expected, interest in my patch is low, so low that nobody even wants to
> > comment on it.
>
> I think its just that fairly few of the active devs read this forum.
> I could be wrong, though.

Possibly; but I sent a link to this forum discussion to mamedev soon after sending my patch, to ask that if they would like to discuss the patch with me that we do it here. I don't know where else to do it. If there is communication going on in the MAME team they do a good job of hiding it.



Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


Re: libmame patch discussion please new [Re: Robbbert]
#268141 - 11/07/11 09:04 AM



> As for this project, although the page doesn't say so, I'm guessing it will be a DLL
> plus a 'front-end' portion?

libmame is the DLL part. There is no front-end portion, at least not of libmame. libmame is a way to enable using the MAME engine directly in other programs, including such things as a front-end, which in fact is what I am doing, although I am not really even close on my front-end yet.

> As some may remember, MESS was exactly like that and it caused all sorts of issues.
> When it was merged into one executable, those problems went away. So, Bryan, despite
> all the effort you've obviously put into this, I kinda doubt it would be accepted
> (once bitten, twice shy sort of thing). My personal view is that I don't care how the
> internals are put together as long as it works.

I wonder what the issues are. I have used libmame quite extensively and there haven't been any issues to speak of. And being able to run MAME directly within the front-end (in my case, segragated by a shared memory segment and process boundary, but the effect is the same) does open up the door for unique features.



Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


Re: libmame patch discussion please new [Re: lharms]
#268142 - 11/07/11 09:10 AM


> Typically people run one at a time. The ability to switch
> out the 'heart' of the program is not really useful just
> due to the way things are distributed.

One nice thing that you can do if you can run MAME in the way that libmame allows is you can run multiple games simultaneously. This doesn't sound that useful but it is nice to be able to pause games that don't allow save state indefinitely while you run another game, in a way that is completely seamless within the frontend.

Also it's nice to start up a new game even before the old game has shut down, it makes the startup of games faster. One goal I have for my frontend is for it to be *fast* to start games; what I do is that I present a typical list-of-games interface and as the user scrolls through them, if you pause on any game for 1/4 of a second it automatically starts it up. The game doesn't take over the frontend or anything, it's just there playing in a small window (rendered within a virtual arcade cabinet) and if you continue to scroll, then the next time you hover on a game for 1/4 second, *that* game is immediately started even before the old one has been completely shut down. This produces an incredibly quick and seamless game start-up experience. By the time you've finally focused your eyes on the screen and are ready to press the select button to play the game, it's already started up.

> If it were me I would leave the .lib/.dll idea alone
> and concentrate more on the proper abstraction in the
> code.

I think of libmame as a first step. My belief is that the "proper abstraction" is: everything necessary to actually emulate games and produce a stream of textures and audio segments representing the running game, should be the true heart of MAME, and is all that MAME proper should contain. Even the basic MAME command-line frontend, if it were to be supplied integrated with the MAME source, should be a layer around the core engine library, a peer of all other frontends. As it is now the MAME command line interface is intertwined with the engine itself and there is no clean abstraction for other frontends to interface to.

libmame provides that abstraction, and if it were incorporated into MAME, I think the next logical step would be to move *all* front-endish functionality out of the MAME engine and into the frontend layer.

But I highly doubt this is going to happen, because I think that this concept of the MAME engine being useful outside of the MAME command-line frontend is not something that I think has buy-in from the MAME developers.

> I would be semi interested in the thinking behind
> cleaving it into two parts. Maybe I am seeing it wrong?

You are seeing it like I see it, but like I said, this is not the same as seeing it as the MAME team sees it.



etabeta
Reged: 08/25/04
Posts: 2036
Send PM


Re: libmame patch discussion please new [Re: lharms]
#268257 - 11/09/11 10:02 AM


> I am sort of interested. What sort of issues? As I dont really follow MESS too
> closely.
>

MESS was built as a single large .dll with two smaller exe (MESS and MESSUI) which used extensively the .dll. the issue was that this made debugging with GDB basically useless (symbols got basically lost in the .dll passage)

I have never investigated if there was any way to fix it keeping the .dll alive, but since MAME was instead released as two separate exe the maintainer simply switched to compiling two separate exe for MESS too.



Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


Re: libmame patch discussion please new [Re: etabeta]
#268286 - 11/09/11 08:11 PM


> > I am sort of interested. What sort of issues? As I dont really follow MESS too
> > closely.
> >
>
> MESS was built as a single large .dll with two smaller exe (MESS and MESSUI) which
> used extensively the .dll. the issue was that this made debugging with GDB basically
> useless (symbols got basically lost in the .dll passage)

Uh ... so, when you want to do debugging, build a static version; this doesn't preclude using a .dll mechanism for normal non-debugging use.

Also I don't know why symbols would be lost. I debug all of the time with mame compiled as a shared library (.so on Linux) and this works just fine, I've never had a problem.

I'm not sure who in their right mind would do debugging on Windows anyway. I find it to be far too flaky to trust with intense debugging sessions.



R. Belmont
Cuckoo for IGAvania
Reged: 09/21/03
Posts: 9713
Loc: ECV-197 The Orville
Send PM


Re: libmame patch discussion please new [Re: etabeta]
#268343 - 11/10/11 05:37 PM


> MESS was built as a single large .dll with two smaller exe (MESS and MESSUI) which
> used extensively the .dll. the issue was that this made debugging with GDB basically
> useless (symbols got basically lost in the .dll passage)

The other problem is that GCC on Windows has no way to control global symbol visibility, so any symbol that isn't local is exported. And the Windows dynamic loader gets *extremely* slow in the presence of a DLL with more than about 1500 exported symbols, even on very fast systems. M1 on Windows has this problem - it's why there's a long dramatic pause when you start either the CLI frontend or Bridge. I build Audio Overload in Visual Studio for precisely that reason, and AO boots up instantaneously in spite of having a very similar amount of code to M1.



Bryan Ischo
MAME Fan
Reged: 03/28/10
Posts: 358
Send PM


Re: libmame patch discussion please new [Re: R. Belmont]
#268350 - 11/10/11 08:13 PM


> > MESS was built as a single large .dll with two smaller exe (MESS and MESSUI) which
> > used extensively the .dll. the issue was that this made debugging with GDB
> basically
> > useless (symbols got basically lost in the .dll passage)
>
> The other problem is that GCC on Windows has no way to control global symbol
> visibility, so any symbol that isn't local is exported. And the Windows dynamic
> loader gets *extremely* slow in the presence of a DLL with more than about 1500
> exported symbols, even on very fast systems. M1 on Windows has this problem - it's
> why there's a long dramatic pause when you start either the CLI frontend or Bridge. I
> build Audio Overload in Visual Studio for precisely that reason, and AO boots up
> instantaneously in spite of having a very similar amount of code to M1.

Maybe I'm doing something wrong, but I am using a linker version script when building libmame on Windows to export only the LibMame symbols from the .dll. Here is the script:


Code:


{
global: LibMame_*;
local: *;
};



The result of using this version script (gcc option --Wl,--version-script=libmame.version) is that libmame.dll only exports about two dozen LibMame symbols and nothing else.

This is sufficient for libmame since only its external API should be seen by anybody; all of the other MAME symbols that are defined for drivers, etc, are not needed outside.

Of course, if you want to do debugging, then you'll have to turn this off, but if you're doing debugging, just built the thing statically.

EDIT:

I'd like to also mention, as an aside, that the PIC requirement for shared object libraries on Linux makes MAME run approximately 10% slower than without PIC. When I benchmarked using libmame as a shared object library, I got approximately 10% worse frames per second (games running unthrottled) than when compiling libmame as a static library.

For most games, this hardly matters, but for those for which that 10% takes the game from always playable to sometimes hiccuping, you could just compile libmame as a static library (no PIC code) and link your program like that. You end up with a ginormous program but if you're in a dedicated environment like a cabinet, it's acceptable.

In fact aside from disk space there is hardly a reason to use libmame as a shared object library (unless you are a pure C program which cannot link against static libmame); it's not like you're going to get the benefit of sharing copies of the library in memory since you're not going to be running two MAME engines at the same time ...

Edited by Bryan Ischo (11/10/11 08:18 PM)



R. Belmont
Cuckoo for IGAvania
Reged: 09/21/03
Posts: 9713
Loc: ECV-197 The Orville
Send PM


Re: libmame patch discussion please new [Re: Bryan Ischo]
#268430 - 11/11/11 11:04 PM


> it's not like you're going to get the benefit of sharing copies of the
> library in memory since you're not going to be running two MAME engines at the same
> time ...

With working Ethernet connections to emulated systems in MESS, use cases for that are certainly coming into focus.



etabeta
Reged: 08/25/04
Posts: 2036
Send PM


Re: libmame patch discussion please new [Re: Bryan Ischo]
#268611 - 11/15/11 09:51 AM


> Uh ... so, when you want to do debugging, build a static version; this doesn't
> preclude using a .dll mechanism for normal non-debugging use.
>

at time, the MESS makefile was specifically built around the idea to have a single .dll with two small exe, so that it was not so easy to build a static version on demand.

I'm sure that an alternative solution would have been possible, without removing the dll, but it had been decided to simply change the makefile to match the MAME one.


Pages: 1

MAMEWorld >> Programming
View all threads Index   Threaded Mode Threaded  

Extra information Permissions
Moderator:  Pi 
0 registered and 2 anonymous users are browsing this forum.
You cannot start new topics
You cannot reply to topics
HTML is enabled
UBBCode is enabled
Thread views: 3637