Jump to content
Search In
  • More options...
Find results that contain...
Find results in...
Xymph

LmpStats, a PHP library to collect demo statistics

Recommended Posts

My query into specifications for source port demo formats, and the work to describe built-in/included demos for PWADs on the Doom Wiki, turned into a one-thing-leads-to-another project. :-)

 

After working on the vanilla and Boom formats, the (G,L)ZDoom family with its array of demo commands in compressed tics looked like an interesting (de)coding challenge.  But because of its myriad of version variations it took a lot more work than initially guestimated, including checks into the source code for all ZDoom/LZDoom and many GZDoom versions, and testing on samples from DSDA of intermediate versions for which sources appear to be lost in the mists of time.

 

In comparison, support for Doom Legacy's demo commands and compressed tics came around easier, also because all source versions are still available.

 

The result is LmpStats, a simple one-function library to collect statistics from a large (but incomplete) variety of demo formats.

Here is the repository: https://github.com/Xymph/LmpStats

 

It's been tested on over 1,000 .lmp files but that doesn't rule out the possibility of a parsing issue in rare cases. ;)

The checks for signatures aren't as detailed as outlined here, because that didn't appear to be necessary for correctly extracting the desired statistics.  Nor was extracting the compatibility levels or (sometimes large) set of detailed options in some formats.

 

Some ZDoom format variations were particularly puzzling, as changes had been made without bumping the GAMEVER (real version) number.  The main problem happened with the DEM_INVUSE command, which in the v2.0.90 source code became 4 bytes (initially revealed via this v2.0.94f demo) and also removes several DEM_* commands and adds 4 new ones.  Yet GAMEVER remained 0x202 as it had been since v2.0.48, until the bump to 0x203 in v2.0.97. A somewhat kludgy -z9 flag needs to be used when analyzing v2.0.90-96 .lmp's.

 

Similar situations with less impact appeared around ZDoom v2.6.0 and GZDoom v1.1.05. In the former case, before v2.6.0 the map name was stored in 8 bytes as a null-terminated string, with remaining bytes also nulled.  In sample v2.6-2.7 demos (DEMOGAMEVERSION 0x219), those bytes up to the 8th one seem no longer null-initialized. Only in 2.8.0 with the bump to 0x21D was the format changed to a variable-length name terminated by a single null-byte.

 

And in the latter case, the single-byte UCMDF_BUTTONS value in demo commands was changed into a variable multi-byte structure, while keeping DEMOGAMEVERSION at 0x20C.  But apparently the sample v1.1.06 demos from DSDA don't use this yet, so no parsing problem emerged.  If anyone has further insights or different interpretations for these cases, please let me know.

 

I haven't been able to find any samples of TASDoom v1.10 or longtic v1.11 demos so pointers to those are welcome. Doomsday appears to use its own compressed tic structure, and I may look into that later.

 

Feedback, improvements, and info on missing formats are welcome.

Share this post


Link to post
On 3/27/2021 at 12:51 PM, Xymph said:

sources appear to be lost in the mists of time

 

Analysis of [ZDoom & GZDoom sources](https://zdoom.org/files/)

 

I'll tell you the one fact I know, in case it's not common knowledge: some versions aren't on zdoom.org, and @drfrag was nice enough to make a list: 1 2.

Share this post


Link to post
On 4/28/2021 at 12:29 AM, Xeriphas1994 said:

I'll tell you the one fact I know, in case it's not common knowledge: some versions aren't on zdoom.org, and @drfrag was nice enough to make a list: 1 2.

Thanks, in the end one relevant version (2.0.13) was missing from my list - now added.

Share this post


Link to post

Somehow I completely missed this when you made the original post. Great work with the library and thanks for releasing it under a free software license. If only it was written in a more commonly used scripting language so people wouldn't have to install PHP for one library :P

 

On 3/27/2021 at 5:51 PM, Xymph said:

I haven't been able to find any samples of TASDoom v1.10 or longtic v1.11 demos so pointers to those are welcome.

You can get these by using PrBoom+. TASDoom is -complevel 6 and you'll get 1.11 demos by using -longtics with a vanilla complevel like 2/3/4.

 

TASDoom moves the last byte in a tic to be the first byte so you get tics that looks like this:

0   50   40    0
0   50   40    0
1   50    0    0
1   50    0    0
1   50    0    0

 

rather than the usual:

50   40    0    0
50   40    0    0
50    0    0    1
50    0    0    1
50    0    0    1

 

Longtics use 2 bytes per turn instead of 1 so there are 5 bytes per tic instead of the usual 4.

 

On 3/27/2021 at 5:51 PM, Xymph said:

Feedback, improvements, and info on missing formats are welcome.

Not sure how much effort you want to invest into ancient and rarely formats but here's some other stuff I know about:

222 - RUDE extended demo format

"DM64" - Doom 64

\x00\x00\x00\x00#\x00\x00\x00 - where \x00 is a null byte and # is a non-null byte with a variable value (don't know what it actually represents), old Zdaemon .zdo demos

"ZDD" - current ZDaemon format (.zdd)

 

There's certainly a lot more, especially for historical source ports, but I didn't go that deep down the rabbit hole myself. Don't see the point, really, when I don't even know about any publicly available demos for them.

Edited by Keyboard_Doomer

Share this post


Link to post
22 hours ago, Keyboard_Doomer said:

If only it was written in a more commonly used scripting language so people wouldn't have to install PHP for one library :P

Your personal preferences say nothing about how common a language is. ;-)

 

It had to be PHP in order to be invoked from my wiki bot scripts, which are in that language, and are in turn based on the Wikimate interface to the MediaWiki API. Also, a Ruby library already exists, and my Python experience is too elementary for something like this. So there you go... :)

 

Quote

You can get these by using PrBoom+. TASDoom is -complevel 6 and you'll get 1.11 demos by using -longtics with a vanilla complevel like 2/3/4.

TAS v1.10 support implemented.

 

v1.11 was already implemented, but now that I tested with a complevel 4/longtics demo, I wonder why the wiki states that there's an expansion byte in the header between episode and map. My sample didn't have that. Would you know more about that?

 

Quote

Not sure how much effort you want to invest into ancient and rarely formats but here's some other stuff I know about:

222 - RUDE extended demo format

Easy enough, support implemented, thanks.

 

Quote

"DM64" - Doom 64

\x00\x00\x00\x00#\x00\x00\x00 - where \x00 is a null byte and # is a non-null byte with a variable value (don't know what it actually represents), old Zdaemon .zdo demos

"ZDD" - current ZDaemon format (.zdd)

 

There's certainly a lot more, especially for historical source ports, but I didn't go that deep down the rabbit hole myself. Don't see the point, really, when I don't even know about any publicly available demos for them.

Right, my main goal was to cover as many versions from the DSDA list as feasible, within reasonable effort. I spent some time in the Doomsday 1.7.8 source but couldn't figure out its custom demo format, so gave up.

 

I collected a bunch of .zdo & .zdd files (v1.08-1.10) from DSDA but since they're proprietary formats with proprietary source code (only ancient v1.06.11 available), it's impossible to support those. That one source release suggests it used a variant of ZDoom's format at the time, and that was time consuming enough the first time already for real ZDoom.

 

Not sure where to look for the DM64 format, but its absence on DSDA indicates it's not worth the effort either.

Supporting historical ports that use variants on vanilla formats (like RUDE) could still be interesting.

Share this post


Link to post
4 hours ago, Xymph said:

I wonder why the wiki states that there's an expansion byte in the header between episode and map

I believe this actually refers to Doom Classic. I don't have a demo to investigate so I don't know what's the first byte in this version (but it would kind of suck if it's either 109 or 111).

 

4 hours ago, Xymph said:

I collected a bunch of .zdo & .zdd files (v1.08-1.10) from DSDA but since they're proprietary formats with proprietary source code (only ancient v1.06.11 available), it's impossible to support those.

I imagine the devs might be willing to disclose the format at least for the deprecated version but considering that this format records activity on a server and hence needs to keep track of a LOT of stuff unrelated to player movements I think it's fairly complex and absolutely not worth it for the purposes here. However, you could easily at least extract the port version from the .zdd format if you really wanted to. Here's a Python code I use for that:

 

 

 

def getZDaemonVersion(header):
    start_byte = 46

    zdaemon_version_chars = []
    current_byte = start_byte
    while True:
        char = chr(header[current_byte])
        if char == "\x00":
            break
        zdaemon_version_chars.append(char)
        current_byte += 1

    zdaemon_version = "".join(zdaemon_version_chars)
    return "ZDaemon {}".format(zdaemon_version)
 


 

4 hours ago, Xymph said:

Not sure where to look for the DM64 format

There's this single Doom 64 EX demo where I got the information from. It's my understanding that the demo format should be compatible with the original Doom 64 but well, I wouldn't know.

 

4 hours ago, Xymph said:

I spent some time in the Doomsday 1.7.8 source but couldn't figure out its custom demo format, so gave up.

Some time ago I briefly tried to use the Doomsday source just to see if the port version is written somewhere to the demo but I see you and I have similar experience with the code, haha.

 

PHP tangent, you may consider not even opening this spoiler :)

 
4 hours ago, Xymph said:

Your personal preferences say nothing about how common a language is. ;-)

I have no intention to make this into a completely pointless argument but it's really not just a personal preference... well, other than personal preference for Linux, I guess :P. From my experience:
Python - very commonly used in open source projects, pre-installed on every Linux distro I ever used
Ruby - occasionally used in open source projects, pre-installed on every Linux distro I ever used
Perl - very common historically, still occasionally encounter some open source projects in it, pre-installed on every Linux distro I ever used (no, not suggesting Perl would be a good option :P just feel like I need to mention it in this context)
JavaScript, Lua - don't remember encountering either as the main language in a small open source project but commonly used for other stuff which make them still more likely to be installed than PHP on the computer of someone interested in coding in general; I happen to have both installed as dependencies to other programs
PHP - obviously used a lot for websites but this is literally the first non-web code I ever encountered written in it, not pre-installed on most Linux distros

 
But of course, the fact that you need the library to interface with other existing PHP scripts make it the right choice so sorry for all this pointless wall of text :). Don't know about this Ruby library, do you have a link?

Edited by Keyboard_Doomer

Share this post


Link to post
On 5/14/2021 at 7:07 PM, Keyboard_Doomer said:

I believe this actually refers to Doom Classic. I don't have a demo to investigate so I don't know what's the first byte in this version (but it would kind of suck if it's either 109 or 111).

Will look into that later.

 

Quote

I imagine the devs might be willing to disclose the format at least for the deprecated version but considering that this format records activity on a server and hence needs to keep track of a LOT of stuff unrelated to player movements I think it's fairly complex and absolutely not worth it for the purposes here.

Yeah, and considering this flamew^Wdiscussion any willingness is exceedingly unlikely.

 

Quote

However, you could easily at least extract the port version from the .zdd format if you really wanted to. Here's a Python code I use for that:

Thanks, implemented (in two lines of PHP :) ).

 

Quote

There's this single Doom 64 EX demo where I got the information from. It's my understanding that the demo format should be compatible with the original Doom 64 but well, I wouldn't know.

v2.5+ support implemented too, and GitHub updated.

Prior versions (at least 2.3 and 2.2) awkwardly used a version byte of 1, conflicting with old vanilla. Also had 6-byte tics, v2.5 uses 8 bytes. So I'm ignoring that older format. And v1.4 used version byte 116, surprisingly. I suppose that could be covered as yet, even though no samples exists.

 

That v2.5 sample is described (in the .txt) to have length 36:09. But lmpStats counts 67990 tics, or a running time of 32:22.57. Not sure why the difference, possibly intermission pauses?

 

Quote

PHP tangent, you may consider not even opening this spoiler :)

  Hide contents

I have no intention to make this into a completely pointless argument but it's really not just a personal preference... well, other than personal preference for Linux, I guess :P. From my experience:
Python - very commonly used in open source projects, pre-installed on every Linux distro I ever used
Ruby - occasionally used in open source projects, pre-installed on every Linux distro I ever used
Perl - very common historically, still occasionally encounter some open source projects in it, pre-installed on every Linux distro I ever used (no, not suggesting Perl would be a good option :P just feel like I need to mention it in this context)
JavaScript, Lua - don't remember encountering either as the main language in a small open source project but commonly used for other stuff which make them still more likely to be installed than PHP on the computer of someone interested in coding in general; I happen to have both installed as dependencies to other programs
PHP - obviously used a lot for websites but this is literally the first non-web code I ever encountered written in it, not pre-installed on most Linux distros

But of course, the fact that you need the library to interface with other existing PHP scripts make it the right choice so sorry for all this pointless wall of text :).

 

Ditto:
 

Spoiler

 

I was defining "common" more from the angle of public surveys, which put PHP around #3 of the scripting languages (though regardless of command-line/web purpose) after Python and JS. But as a Unix/Linux person since the mid-'80s (long before my first DOS/Win3 PC) I share your view on what is commonly included in distros.

As it happens, I first picked up PHP in 2007 for a non-web project that had been abandoned by another programmer, and because it would be useful to learn due to its popularity. Plenty web-based projects big and small followed in later years for me. I also did quite some Lua work around 2011-2014 but am too rusty in it now, and use JS regularly for web projects but don't find it a natural pick for offline work.

My other main scripting language (besides Bash, of course) is indeed Perl because all of bluesnews.com is built in it. But the Perl API interfaces to MediaWiki didn't appeal to me and since MediaWiki itself is also written in PHP, one of the smaller PHP libraries seemed a logical choice. I also expanded/improved this Wikimate with a ton of stuff I needed for the DoomWiki bot scripts.

PHP is an easy language but it can also easily become messy. ;)  LmpStats is not particularly cleanly structured, but that is at least in part due to all the myriad of version checks being messy themselves.

So there's your reciprocal wall of text. ;-)

 

 

Quote

Don't know about this Ruby library, do you have a link?

It was linked a few posts up from yours in the old spec thread.

Edited by Xymph

Share this post


Link to post
4 hours ago, Xymph said:

That v2.5 sample is described (in the .txt) to have length 36:09. But lmpStats counts 67990 tics, or a running time of 32:22.57.

Doom 64 uses 30 tics per second, rather than 35.

 

4 hours ago, Xymph said:

It was linked a few posts up from yours in the old spec thread.

Ah yes, kraf's lmp tool. It has a lot narrower focus as far as demo formats go so thanks again for making this library and making the code freely available.

 

 

Spoiler
4 hours ago, Xymph said:

Thanks, implemented (in two lines of PHP :) ).

Well, I could have just used regex in Python as well :P I guess the only reason I didn't is that I didn't want to read a random, meaningless amount of bytes.

 

PHP is indeed very common, just not for general purpose coding like so many other languages, we seem to be in agreement there. :)

 

Share this post


Link to post
On 5/15/2021 at 5:07 AM, Keyboard_Doomer said:

There's this single Doom 64 EX demo where I got the information from. It's my understanding that the demo format should be compatible with the original Doom 64 but well, I wouldn't know.

They aren't. Doom64's demo format is actually quite simpler, and strangely being quite dependent on how the N64 controller operates. The 2020 port has these demos in their original format in the IWAD and can play them back.

Share this post


Link to post
8 hours ago, Keyboard_Doomer said:

Doom 64 uses 30 tics per second, rather than 35.

Ah. In that case, 67990 tics run for 37:46.33, 97 seconds longer than 36:09, but those would be intermission pauses not tallied in the level playtimes, I suppose.

Share this post


Link to post

Yeah, it's 30 maps and the wipe screen effect after the level exit + intermission in Doom 64 take a while even when you're trying to go through them as fast as you can so 1:37 seems like a reasonable time.

Share this post


Link to post
On 5/14/2021 at 7:07 PM, Keyboard_Doomer said:

I believe this actually refers to Doom Classic. I don't have a demo to investigate so I don't know what's the first byte in this version (but it would kind of suck if it's either 109 or 111).

It is 111 indeed, or 112 if a debug flag is defined. So LmpStats needed a flag to distinguish it from "regular" PrBoom+ longtics format.

Fortunately the available source made it pretty easy to add support for this format. I've also added descriptions to the wiki of the additional fields compared to vanilla.

 

Luckily, the D3 BFG edition was on -50% sale on Steam this weekend, so I picked it up for a tenner. Kind of silly to duplicate my boxed copies of the main game and expansion, but the things I do for research... :-D Unfortunately, demo recording turned out to be broken, but after adding the Doom BFA port this allowed recording and analyzing some demos. Yet another hurdle is that -timedemo doesn't work to verify my tic counts against the engine's, but that will hopefully be addressed in a future update.

 

GitHub has the new LmpStats v0.9.0 release (with a tag), which also adds support for Doom64 EX v1.4 and a few smaller improvements.

 

So... any other historical formats that could be covered?

Share this post


Link to post
On 5/16/2021 at 2:01 AM, Edward850 said:

They aren't. Doom64's demo format is actually quite simpler, and strangely being quite dependent on how the N64 controller operates. The 2020 port has these demos in their original format in the IWAD and can play them back.

I found the DEMOxLMP lumps in my IWAD. According to the D64 tech bible, the first 56 bytes are button settings, followed by 4-byte tics. That's clear from hex dumps, but a few things puzzle me.

There are four lumps, but only three demos play: on MAPs 03, 09 and 17 -- apparently hardcoded in the engine. So why is the fourth demo not played? minutes

 

And all four are exactly 16000 bytes, yet with stopwatch in hand the above maps are played for about 1:07, 0:41 and 1:08 mins.

Hex dumps show no more tic data (i.e. all zeroes) follows from offsets 0x1F8C, 0x1400, 0x20E0, and 0x3184 onwards, respectively.

That makes for 2005, 1266, 2090 and 3155 tics, or 66.8, 42.2, 69,7 and 105.2 seconds (at 30 tics/sec). That correlates with the stopwatch. So why are the lumps filled out to 16000 bytes? N64 memory layout?

Share this post


Link to post

Demo's are less my forte, but since TASDoom was mentioned, maybe look at a few more:

  • TASMBF
  • XDRE (PrBoom based, so i reckon the format is the same, but who knows)
  • DRE (PrBoom based, predecessor to XDRE)
  • Timer (DOSDoom based, by Aurikan. This was listed on the DSDA archives but i can't think of a single demo that made use of it. It being listed however might suggest that demo's exist for it though.)
  • Chase (Also DOSDoom based, by Aurikan. It apparently can play back demo's made for it, but there is no such thing a description of its format. Same limitations as Timer. There might be demo's made with it..)

Offshoots:

Then there is the works of Yonathan Donner; DdQstats and Doom 2 Done Quick.

 

DdQStats:

Doom 2 Done Quick:

  • Doom 2 Done Quick uses a custom exe released on June 10, 1998, called D2DQ.exe. It is actually another port, based off of DOSDoom 0.45.  File name is d2dq2116.zip by Yonatan Donner.
  • D2DQ uses a .cam and shows the demo from a camera perspective but also in first person, and can be done on the fly. The program was called MakeCam.

Maybe useful @Xymph and perhaps @kraflab for some demo-recording history rarities?

 

PS: Hmm, the MakeCam link has gone defunct. I didn't know that was possible straight from Archive.org. It used to exist, atleast. I may have a local copy.

PPS: Here is the documentation of LMPC with a extensive background on all its formats.

Edited by Redneckerz

Share this post


Link to post
3 hours ago, Xymph said:

There are four lumps, but only three demos play: on MAPs 03, 09 and 17 -- apparently hardcoded in the engine. So why is the fourth demo not played? minutes

The 4th demo is hidden. The password "rvnh3ct1cd3m0???" unlocks it (when unlocked, it'll play at the end of the loop).

3 hours ago, Xymph said:

So why are the lumps filled out to 16000 bytes? N64 memory layout?

Unsure but that seems like a pretty good guess. It may also be the nature of how they were recorded, though I couldn't say how that was done.

Share this post


Link to post
13 hours ago, Edward850 said:

The 4th demo is hidden. The password "rvnh3ct1cd3m0???" unlocks it (when unlocked, it'll play at the end of the loop).

Doh, forgot to check the wiki. Thanks.

 

Demos table added.

Share this post


Link to post
On 5/17/2021 at 11:16 PM, Redneckerz said:

Demo's are less my forte, but since TASDoom was mentioned, maybe look at a few more:

  • TASMBF

v2.03 format, same as MBF.

 

Quote
  • XDRE (PrBoom based, so i reckon the format is the same, but who knows)
  • DRE (PrBoom based, predecessor to XDRE)

These appear to be demo editors, not ports to record with?

 

Quote
  • Timer (DOSDoom based, by Aurikan. This was listed on the DSDA archives but i can't think of a single demo that made use of it. It being listed however might suggest that demo's exist for it though.)
  • Chase (Also DOSDoom based, by Aurikan. It apparently can play back demo's made for it, but there is no such thing a description of its format. Same limitations as Timer. There might be demo's made with it..)

Both use v1.10 TASDoom format. That is, the original Chase release -- the v1.63 exe segfaults in DosBox.

Also, the original release counts 1 tic fewer in -timedemo than lmpStats reports. Either Chase miscounts tics, or the format contains 4 additional header bytes that lmpStats shouldn't count as a tic. But without sources that's impossible to determine, and since the version byte doesn't allow distinguishing it from TASDoom/Timer anyway, I'm not gonna lose sleep over the one tic. :-)

Timer records okay but also segfaults with -timedemo, so no telling how the engine counts tics.

 

Anyway, I only added these two and TASMBF to the README.md info.

 

Quote

DdQStats:

Doom 2 Done Quick:

Maybe useful @Xymph and perhaps @kraflab for some demo-recording history rarities?

Both appear to use v1.09 vanilla format. ddqstats can record but -timedemo doesn't report tics (only its own stats). d2dq page-faults when attempting to -record.

 

Quote

PS: Hmm, the MakeCam link has gone defunct. I didn't know that was possible straight from Archive.org. It used to exist, atleast. I may have a local copy.

As it turns out, I still have a copy in my '90s archive too. But it's not relevant to .lmp formats.

 

Quote

PPS: Here is the documentation of LMPC with a extensive background on all its formats.

Yes, linked from the wiki, but it doesn't go beyond vanilla formats.

 

Thanks for all the above.

Share this post


Link to post
6 hours ago, Xymph said:

v2.03 format, same as MBF.

Expected, nevertheless, appreciated :)

6 hours ago, Xymph said:

These appear to be demo editors, not ports to record with?

They are indeed, but they are used to build TAS demos. I was (wrongfully) assuming that such editors, when crafting their demo's, utilize their own unique formats.

6 hours ago, Xymph said:

 

Both use v1.10 TASDoom format. That is, the original Chase release -- the v1.63 exe segfaults in DosBox.

Also, the original release counts 1 tic fewer in -timedemo than lmpStats reports. Either Chase miscounts tics, or the format contains 4 additional header bytes that lmpStats shouldn't count as a tic. But without sources that's impossible to determine, and since the version byte doesn't allow distinguishing it from TASDoom/Timer anyway, I'm not gonna lose sleep over the one tic. :-)

Unfortunately i have no sources available beyond these two builds. Its already something that these survived - One was far more common than the other (original release). Consider it an exception ;)

6 hours ago, Xymph said:

Timer records okay but also segfaults with -timedemo, so no telling how the engine counts tics.

I realized later on that i did have (i believe) some more info available, but i don't think it described exact ticrates. If interested ill provide a link.

 

Either way, thanks for adding them to the readme. Glad to be of help for your statistical endeavors :)

6 hours ago, Xymph said:

Both appear to use v1.09 vanilla format. ddqstats can record but -timedemo doesn't report tics (only its own stats). d2dq page-faults when attempting to -record.

There is a rather extensive D2DQ text file that did describe things, but i guess it does not describe records? (I can't really check at the moment.)

6 hours ago, Xymph said:

As it turns out, I still have a copy in my '90s archive too. But it's not relevant to .lmp formats.

Maybe this can be mirrored? I don't think anyone will find a use for it, but it may give a good basis for when anyone wants to implement a camera within Doom.

Share this post


Link to post
On 5/19/2021 at 10:49 PM, Redneckerz said:

They are indeed, but they are used to build TAS demos. I was (wrongfully) assuming that such editors, when crafting their demo's, utilize their own unique formats.

Didn't check, but it's likely they use vanilla formats.

 

On 5/19/2021 at 10:49 PM, Redneckerz said:

I realized later on that i did have (i believe) some more info available, but i don't think it described exact ticrates. If interested ill provide a link.

Sure, I'll take a look.

 

On 5/19/2021 at 10:49 PM, Redneckerz said:

There is a rather extensive D2DQ text file that did describe things, but i guess it does not describe records? (I can't really check at the moment.)

Doesn't appear to be.

 

On 5/19/2021 at 10:49 PM, Redneckerz said:

Maybe this can be mirrored? I don't think anyone will find a use for it, but it may give a good basis for when anyone wants to implement a camera within Doom.

Alright, I put it in the idgames archive (zip, txt) but with original timestamp bypassing normal upload, which is prolly why the legacy interface didn't pick it up.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×