Author Topic: Technical side discussion  (Read 164668 times)

Codewalker

  • Hero of the City
  • Titan Network Admin
  • Elite Boss
  • *****
  • Posts: 2,740
  • Moar Dots!
Re: Technical side discussion
« Reply #120 on: July 16, 2015, 03:27:30 AM »
So, bottom line - the colors stored in the SQL database are BGR, and negative colors need to have (1 << 24) added to them before hexing them. I'm presuming that the other colors are encoded the same way as the skin color.

That sounds complicated and probably wrong. Ignore the negative hex value, negative hex is usually meaningless.

It's just a 32-bit unsigned int being interpreted as a signed int. The same as how FXTINT shows up in demorecords iirc.

Code: [Select]
print(hex(221),hex(169),hex(107))
0xdd 0xa8 0x6b
print(hex(-9721635))
-0x945723

Instead, do

Code: [Select]
import struct
bin = struct.pack("i", -9721635)
num = struct.unpack("I", bin)
print(hex(num[0]))
0xff6ba8ddL

Or, if you don't want to use struct and want to do it the slower mathy way that only works for negative numbers,

Code: [Select]
print(hex(2**32 - 9721635))
0xff6ba8ddL

The bytes are in ABGR order because the integer originated on a little-endian architecture.

Quote from: slickriptide link=topic=10956.msg187871#msg187871
One step closer to that costume hash.

The costume hash doesn't really have anything to do with the database storage format. The database isn't intended to be used by other applications and the schema is subject to change at any time. That's why colors are normalized to RGBA hex-digits-as-strings for the hash and for transmission on the wire.

Arcana

  • Sultaness of Stats
  • Elite Boss
  • *****
  • Posts: 3,672
Re: Technical side discussion
« Reply #121 on: July 16, 2015, 05:01:26 AM »
Instead, do

Code: [Select]
import struct
bin = struct.pack("i", -9721635)
num = struct.unpack("I", bin)
print(hex(num[0]))
0xff6ba8ddL

Technically, I think you should do "bin = struct.pack("=i",-9721635)".  I'm not 100% certain, but if there are any budding Mac OSX bot writers out there using python compiled natively, sizeof(int) might be 8.  I know its 4 on Windows whatever, no matter the cpu.  But I think on OSX and Linux gcc will with the right flags compile an 8 byte integer.  I think python struct will expose that.

slickriptide

  • Elite Boss
  • *****
  • Posts: 356
Re: Technical side discussion
« Reply #122 on: July 16, 2015, 06:06:29 AM »
Technically, I should be writing this in C or some language with a sensible grasp of unsigned integers and other niceties.

At this point,though, I'm halfway to being adequate in Python so I suppose I'll keep plugging at it.

I wasn't implying that the database storage formats had anything to do with costume hashing. The *data* in the database, however, is pretty much the meat of the hash. ;-)

Sure, I completely agree. The PC database is not intended for anything but PC, etc... How wasteful is it, though, to take a perfectly functional, structured relational database, unload it to a passel of individual, minimally structured and unrelated flat text files, and then munge all of those individual files back into a database whose structure is nearly identical to what it originally had?

Yeah, the "proper" thing to do is to bite the bullet and write a Costume2SQL() parser but it sure seems like a bitch to have to do it.

For now, anyway, it's more sensible to dump the costume tables and use them my for own purposes and if the format changes or the Emperor of Blefuscu ordains that colors be stored in a different order then I'll deal with that.

***EDIT***

The reason the 24-bit thing feels wrong is that it masks off the last byte. We don't actually care about the last byte though - it's always FF.

« Last Edit: July 16, 2015, 01:12:05 PM by slickriptide »

slickriptide

  • Elite Boss
  • *****
  • Posts: 356
Re: Technical side discussion
« Reply #123 on: July 16, 2015, 01:31:16 PM »
I realized something this morning.

A bot will be able to edit a base.

It won't be able to see what it's doing, of course, but it will be able to follow a script that it knows will pick up object A and move it to point B or replace it with another object.

Theoretically, a bot could play a basic "shell game" by dropping three crates on the ground  then announcing the shuffling of the crates or making some visual show of sliding them around and putting something small under one of the crates to be the "pea".

The limits of possibility would mostly be the ability of the bot to ignore placement restrictions that the client UI imposes on a player, just like ArcanaBot ignores gravity by hard-setting her position in space while the client UI imposes gravity on a player.


Codewalker

  • Hero of the City
  • Titan Network Admin
  • Elite Boss
  • *****
  • Posts: 2,740
  • Moar Dots!
Re: Technical side discussion
« Reply #124 on: July 16, 2015, 01:33:46 PM »
The base edit process won't be over XMPP. That wouldn't make sense. There's no reason for a player to edit another player's base instead of just editing it themselves.

A bot could _upload_ a finished base into whatever mechanism we end up using to share the base map between players. But you don't need a bot to do that anyway since you can just load one from a file.

slickriptide

  • Elite Boss
  • *****
  • Posts: 356
Re: Technical side discussion
« Reply #125 on: July 16, 2015, 01:53:03 PM »
The base edit process won't be over XMPP. That wouldn't make sense. There's no reason for a player to edit another player's base instead of just editing it themselves.

A bot could _upload_ a finished base into whatever mechanism we end up using to share the base map between players. But you don't need a bot to do that anyway since you can just load one from a file.

Yes, you're right. That would involve scripting the client in some way. Scratch that, then or at least table it until the day that a LUA engine is added to the client.

OTOH - If you can load a base file while someone is in the base, then you can still have your bot do "scene changes" on command. The practicality of it would depend on the time involved in "repainting" the base. It might be something a RP group would find interesting.

slickriptide

  • Elite Boss
  • *****
  • Posts: 356
Re: Technical side discussion
« Reply #126 on: July 16, 2015, 03:31:46 PM »
Venting ahead. Skip to the last line for the non-venting content.

Just so to illustrate why I dislike working with .costume files, here are two examples from characters that I saved out to .costume yesterday afternoon.

Code: [Select]
{
BoneScale 0.451
ShoulderScale 0.336
WaistScale 0.158
HipScale 0.226
HeadScales  0,  0,  0
BrowScales  0,  0,  0
CheekScales  0,  0,  0
ChinScales  0,  0,  0
CraniumScales  0,  0,  0
JawScales  0,  0,  0
NoseScales  0,  0,  0
SkinColor  246,  187,  170
NumParts 28
BodyType 1
CostumePart ""
{

and

Code: [Select]
{
Scale -5.336
BoneScale 0.255
ShoulderScale 0.174
ChestScale 0.188
WaistScale 0.176
HipScale 0.208
LegScale 0.005
HeadScales  0,  0,  0
BrowScales  0,  0,  0
CheekScales  0,  0,  0
ChinScales  0,  0,  0
CraniumScales  0,  0,  0
JawScales  0,  0,  0
NoseScales  0,  0,  0
SkinColor  221,  168,  107
NumParts 28
CostumePart ""
{

A comparison of the two shows that neither file contains all of the definitions. Char 1 has no general scale, no chest scale, no leg scale. Char 2 has no body type. What does it mean? The missing scales should default to zero? Default to 1.0? Who knows? Sure, @Codewalker and @Leandro probably have worked it out already but for someone whose only access to a costume is this file, how are you supposed to know that?

What else is missing that I can't even see because I don't know about it?

If I'm building a costume stanza, do I make up values for those things that seem "reasonable" or do I leave them blank and let the client that's looking at me figure it out? This matters because, for instance, the <Appearance> stanza has an attribute called "head=". Presumably this is a HeadScale that's invoked when you change your head shape. I don't typically mess with that, though, so *none* of my .costume files have a HeadScale in them.So, what do I do? What does that mean for the costume hash? Ignore the missing scale? Do I just drop the body type of Char 2 out of the hash? Do I just leave off the Scale attribute for Char 1 and hope for the best?

Yes, I know that @Codewalker has previously defined a Body Type = 0 as "male" but that's not the point. The costume file doesn't tell me that it's a male body type. It doesn't tell me anything at all, so I have to fall back on "Well, it's not this or that so it must be the other". It's a pain in the posterior.

It should hardly be a wonder that anybody would want to skip .costume files entirely and "cheat" by grabbing the costumes straight out of the client's database.

Okay, venting over.

I've decided to do the correct thing and parse the damn .costume files, but that means I need to know what Paragon Chat/CoH uses as a default value when a .costume dispenses with recording one or more attributes. Do they always default to 0 or 0.0?

Codewalker

  • Hero of the City
  • Titan Network Admin
  • Elite Boss
  • *****
  • Posts: 2,740
  • Moar Dots!
Re: Technical side discussion
« Reply #127 on: July 16, 2015, 03:57:58 PM »
All of the scales in a costume file default to 0 if they're left out. I kept it the same for PC to make sure it would be compatible. I agree depending on parser defaults can make it ambiguous, which is why I added the explicit 'omitted if 0' language to the formal definition for PC.

I think "Head" may actually be obsolete and predate the individual face sliders. I snagged the names from the client's built-in costume file parser but didn't check to see if they were actually used. So that one will probably go away.

Arcana

  • Sultaness of Stats
  • Elite Boss
  • *****
  • Posts: 3,672
Re: Technical side discussion
« Reply #128 on: July 16, 2015, 06:17:57 PM »
What else is missing that I can't even see because I don't know about it?

Back when the Architect was in beta, one of the things we were wondering was how the Architect itself stored costumes internally, because it was that size, and not our own local file size, that counted against our mission quote.  So what I did was to start creating costumes.  I created AllBlackMan, who used black as the color for everything and AllWhiteMan who used white as the color for everything.  I made costumes with as few parts as possible, and with as many parts as possible.  I made BlandGuy that moved none of the sliders, and CrazyGirl that moved all of them, and reported differential file sizes.  That gave us an idea of how "expensive" it was to do various things, and answered questions like did certain default colors get dropped from the storage and thus actually make the costume smaller (not that I recall) or did using multiple colors require more storage (generally it did not).

You could do the same: make "MaximalCostumeMan" that moves every slider, adds parts to every possible part node, and turns fx on in as many places as is legal, and save that.  Then use that to test your costume code to make sure you are handling every single field correctly.

Also, Codewalker did state in his costume stanza description that "zeros" could be dropped from transmission.  In fact for certain things he suggested you either SHOULD, or MUST drop them because they could subtly cause hash glitches which then cause invisibility issues (although its possible by now his code guards against that either way).

I got home, wrote up my costume parser, then got sleepy and went to bed.  No idea if it works, because I fell asleep before writing like two lines of SHA code.  I should just hire a twenty year old to write this for me for lunch money.

slickriptide

  • Elite Boss
  • *****
  • Posts: 356
Re: Technical side discussion
« Reply #129 on: July 16, 2015, 07:54:53 PM »
Yeah, it's more the principle of the whole thing rather than the difficulty of any individual bit of it. I shouldn't have to make Max Slider Man and Min Slider Girl. Those silly Cryptic and Paragon Studios devs ought to have anticipated that I would want this information years after their game was dead and packaged it up with full documentation and a bow on top.

And I still feel like using costume files in the first place is a bit like walking six blocks down to the end of the fence separating my yard from my neighbor's yard just so that I can step around the end of it and walk six blocks back up to visit my neighbor.

On the plus side, going the .costume route has made me consider some alternate implementations that may end up being more interesting than going the lazy route.



Arcana

  • Sultaness of Stats
  • Elite Boss
  • *****
  • Posts: 3,672
Re: Technical side discussion
« Reply #130 on: July 16, 2015, 09:17:15 PM »
And I still feel like using costume files in the first place is a bit like walking six blocks down to the end of the fence separating my yard from my neighbor's yard just so that I can step around the end of it and walk six blocks back up to visit my neighbor.

I don't see it that way, for the simple reason that absolutely nothing in the Paragon Chat databases isn't also stored in costume files for me, because its not like I don't trust Codewalker, but its called beta software for a reason.  Whether its Microsoft or Codewalker, I'm not storing the only copy of anything in beta software.  Furthermore, I have a huge library of costumes saved from playing the game, and I would want my bot to be able to tap into that without having to create a character in Paragon Chat and load the costume.  In other words, I consider the costume files to be "the source" and Paragon Chat to be just an intermediary incomplete source when it comes to the question of bots.  I should, in fact, be able to create a costume for a bot by directly creating a costume file and loading it into the bot without having to even run Paragon Chat, or even without having even *seen* Paragon Chat in theory.

To go even further, a bot should be able to create and display a costume without ever touching anything else, not even costume files.  But if you are going to save costumes at all, the costume file seems to be the best way to do so, because you're now using a standard file format other people could use for other purposes.  A bot could enter a zone and record everyone's costumes and write them to costume files.  You could then peruse them later in the costume creator.  If you want to know how someone created a costume, you could have a bot snapshot it for later.  All of this makes costume files better to figure out how to read and write, and none of it would be helped by looking at the Paragon Chat databases.  To be honest, I have yet to even *peek* into them, because while I'm curious, there is literally nothing in there that would be remotely helpful to me at the moment.  In fact as you've experienced, it can actually lead you astray.

On the subject of lazy, this is how I read costume files:

Code: [Select]
pc_costume_dict = {}
pc_costume_dict['Parts'] = {}

PartNum = 0

clevel = 0

for pc_costume_line in pc_costume:
    if pc_costume_line[0] == '\n':
        pass   
    elif clevel == 0:
        if pc_costume_line[0] == '{':
            clevel = clevel + 1
    elif clevel == 1:
        if pc_costume_line[0] == '{':
            clevel = clevel + 1
        elif pc_costume_line[0] == '}':
            clevel = clevel - 1
        else:
            pc_costume_dict[pc_costume_line.split()[0]] = ' '.join(pc_costume_line.split()[1:])

    elif clevel == 2:
        if pc_costume_line[0] == '}':
            clevel = clevel - 1
            PartNum = PartNum + 1
        else:
            if not pc_costume_dict['Parts'].has_key(PartNum):
                pc_costume_dict['Parts'][PartNum] = {}
            pc_costume_dict['Parts'][PartNum][pc_costume_line.split()[0]] = ' '.join(pc_costume_line.split()[1:])
        pass
   
    else:
        print "Debug: parsing error"

And this is how I convert them into ParagonChat-ese:

Code: [Select]
for key in pc_costume_dict:
    if key in scale_list:
        print "Debug: element: " + key + " value: " + pc_costume_dict[key]
        pc_costume_dict[key] = dezero(pc_costume_dict[key])
        print "Debug: converted: " + pc_costume_dict[key]
        pass
    if key in scales_list:
        print "Debug: element: " + key + " value: " + pc_costume_dict[key]
        pc_costume_dict[key] = tuple2packint(pc_costume_dict[key])
        print "Debug: converted: " + pc_costume_dict[key]
        pass
    if key in color_list:
        if key == 'SkinColor':
            print "Debug: element: " + key + " value: " + pc_costume_dict[key]
            pc_costume_dict[key] = colorstr2hex(pc_costume_dict[key])
            print "Debug: converted: " + pc_costume_dict[key]
        else:
            print "Debug: element: " + key + " value: " + pc_costume_dict[key]
            pc_costume_dict[key] = key[5]+colorstr2hex(pc_costume_dict[key])
            print "Debug: converted: " + pc_costume_dict[key]

for part in pc_costume_dict['Parts']:
    for key in pc_costume_dict['Parts'][part]:
        if key in color_list:
            if key == 'SkinColor':
                print "Debug: element: " + key + " value: " + pc_costume_dict['Parts'][part][key]
                pc_costume_dict['Parts'][part][key] = colorstr2hex(pc_costume_dict['Parts'][part][key])
                print "Debug: converted: " + pc_costume_dict['Parts'][part][key]
            else:
                print "Debug: element: " + key + " value: " + pc_costume_dict['Parts'][part][key]
                pc_costume_dict['Parts'][part][key] = key[5]+colorstr2hex(pc_costume_dict['Parts'][part][key])
                print "Debug: converted: " + pc_costume_dict['Parts'][part][key]

It doesn't get a lot more lazy than that.  I didn't even use closures.  I even did extra work forgetting SkinColor was the only non-numbered Color (duh) and I left in my placeholder passes. 

Its hard to be creative when you're doing this an hour or so at a time.  Honestly, I'm a crap programmer unless I really get into the swing.  I have a habit of doing this:

Code: [Select]
def colorstr2hex(colorstr):
##    colorhex = hex(int(colorstr.split()[0].split(',')[0]))[2:] + \
##                hex(int(colorstr.split()[1].split(',')[0]))[2:] + \
##                hex(int(colorstr.split()[2].split(',')[0]))[2:] + 'ff'
    colorhex = "{0:0{1}x}".format(int(colorstr.split()[0].split(',')[0]),2) + \
                "{0:0{1}x}".format(int(colorstr.split()[1].split(',')[0]),2) + \
                "{0:0{1}x}".format(int(colorstr.split()[2].split(',')[0]),2) + 'ff'
    return colorhex

Because first I do it the wrong way, then I realize its the wrong way, then I remember the right way, then I write a hybrid love-child mutant of the wrong way and the right way and call it a day.

Codewalker would probably be horrified to learn I did this:

Code: [Select]
def tuple2packint(tuplestr):
    print "Debug: tuplestr: " + str(tuplestr)
    x = tuplestr.split()[0].split(',')[0]
    #print "debug: x=" + str(x)
    y = tuplestr.split()[1].split(',')[0]
    z = tuplestr.split()[2].split(',')[0]
    xx = abs(int(x * 100))
    yy = abs(int(y * 100))
    zz = abs(int(z * 100))
    if cmp(x,0) == -1:
        xx = xx + 128
    if cmp(y,0) == -1:
        yy == yy + 128
    if cmp(z,0) == -1:
        zz == zz + 128
    # return '00' + hex(zz)[2:] + hex(yy)[2:] + hex(xx)[2:]
    return '00' + "{0:0{1}x}".format(zz,2) + "{0:0{1}x}".format(yy,2) + "{0:0{1}x}".format(xx,2) 

I didn't even guard against overflow, bah.  Notice: same mistake again.  Dr. Dobbs flashback quiz: what was the mistake?

slickriptide

  • Elite Boss
  • *****
  • Posts: 356
Re: Technical side discussion
« Reply #131 on: July 17, 2015, 05:02:01 AM »
Heh. You know you can avoid that whole "split once on space and then split off the comma" business by splitting on ", "?  I started to go the double-split route also until I read the docs again and verified that the separator can be a string and not just a character. ;-)

Furthermore, I have a huge library of costumes saved from playing the game, and I would want my bot to be able to tap into that without having to create a character in Paragon Chat and load the costume.  In other words, I consider the costume files to be "the source" and Paragon Chat to be just an intermediary incomplete source when it comes to the question of bots.  I should, in fact, be able to create a costume for a bot by directly creating a costume file and loading it into the bot without having to even run Paragon Chat, or even without having even *seen* Paragon Chat in theory.

I came around to a similar way of thinking today while I was considering how I wanted to approach the .costume parser. I don't have a library of costumes from the old days (grand total of four and two of those have been overwritten by PC-generated costume files), but when I reviewed my pie-in-the-sky goals for the bot, one of them is that I want to be able to tell it to "fetch!" and have it reach out over the internet to a web repository and pull down a costume or a package of props and accessories and install it on command.

Making everything SQL based was pretty much the antithesis of that.

However, I had a realization - I do need a data structure to store the costume data, and while a bunch of dicts immediately comes to mind, there's another structure available that I have to build anyway in order to interact with Paragon Chat - The costume stanza.

The stanza not only acts as an in-memory data structure for the costume, it's simple to export and it's human-readable. By saving the stanza XML to a text file and naming the text file with the hash, a simple database is automatically created that allows the bot, or a human, to quickly load and display any costume with no computation involved.

Meanwhile, the local SQL database can be used to associate a given stanza with a character name, description, and any other relevant relational data. The bot can even save the stanzas it receives from other players and cache them or otherwise save them for later re-use.

So, I'm meh on costume files but I'm finding the idea of .stanza files to be something interesting that works the same way while providing more functionality than a .costume currently provides.

***EDIT***

Oh, pro tip - If you've got a big library of older costume files, you'll want to add a rule that translates "CostumeFilePrefix    [male | female | huge]"
to the appropriate 'bodytype'.


Arcana

  • Sultaness of Stats
  • Elite Boss
  • *****
  • Posts: 3,672
Re: Technical side discussion
« Reply #132 on: July 17, 2015, 05:05:01 AM »
So, I sort of have costume hashes computing, but things are not matching up.  First problem I noticed: what is in a costume file is not necessarily what is in the costume.  I took the costume that I saved on the last day of the game, loaded it into Paragon Chat, then immediately resaved it.  The two files are not identical.  There are some subtle differences (in particular, upper/lower cases changes) and a few major ones (an unused part disappeared in the newly saved version, which implies I24 had different costume part checking than I23).  To eliminate these problems I loaded and saved a costume file and hashed that.  The hashes still don't match.

Question: since hashing is case sensitive, is case forced all up or all lower for consistency?  The sample suggested it was not ("Leather" retained its capitalization).  Also, this probably has nothing to do with anything, but I24 itself *created* a Part that was not in the I23 costume file, and weirdly its not a functional part entry: it looks like this in Part 25:

Code: [Select]
CostumePart ""
{
Geometry none
Texture1 none
Texture2 none
DisplayName P546439883
RegionName "Lower Body"
BodySetName NewSkirts
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}

Here's the costume in question (the I24 version):

Code: [Select]
{
BoneScale -0.85
ChestScale -0.53
WaistScale -0.78
HeadScales  0,  0,  0
BrowScales  0,  0,  0
CheekScales  0,  0,  0
ChinScales  0,  0,  0
CraniumScales  0,  0,  0
JawScales  0,  0,  0
NoseScales  0,  0,  0
SkinColor  246,  187,  170
NumParts 28
BodyType 1
CostumePart ""
{
Geometry shorts
Texture1 skin_tights
Texture2 miniskirt_1
DisplayName P1525729866
RegionName "Lower Body"
BodySetName NewSkirts
Color1  255,  0,  0
Color2  0,  69,  204
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry Tight
Texture1 skin_tights
Texture2 Top_5
DisplayName P566009771
RegionName "Upper Body"
BodySetName Tights/Skin
Color1  255,  0,  88
Color2  212,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry V_fem_Head.GEO/GEO_Head_V_Asym_Standard
Texture1 !v_sf_face_skin_head_10
Texture2 none
DisplayName P687117166
RegionName Head
BodySetName standard
Color1  255,  101,  205
Color2  204,  202,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry Bracer_02
Texture1 Skin_Bracer_02a
Texture2 Skin_Bracer_02b
DisplayName P3937616722
RegionName "Upper Body"
BodySetName Tights/Skin
Color1  255,  253,  0
Color2  212,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry Hi_Heels_02
Texture1 skin_Hi_Heels_02a
Texture2 skin_Hi_Heels_02b
DisplayName P2104750136
RegionName "Lower Body"
BodySetName NewSkirts
Color1  153,  0,  49
Color2  0,  0,  255
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry standard
Texture1 MARTIAL_ARTS_01
Texture2 none
DisplayName P177456852
RegionName "Upper Body"
BodySetName Tights/Skin
Color1  153,  0,  49
Color2  31,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry Long_04
Texture1 Long_01a
Texture2 Long_01b
DisplayName P2681220175
RegionName Head
BodySetName standard
Color1  255,  0,  0
Color2  170,  56,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry Tiara_01
Texture1 Tiara_01a
Texture2 Tiara_01b
DisplayName P2793026233
RegionName Head
BodySetName standard
Color1  204,  202,  0
Color2  212,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry Tight
Texture1 Base
Texture2 Star_10
DisplayName P2281134661
RegionName "Upper Body"
BodySetName Tights/Skin
Color1  0,  0,  0
Color2  255,  253,  76
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry none
Texture1 none
Texture2 none
DisplayName P772741860
RegionName "Upper Body"
BodySetName Tights/Skin
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  31,  31,  31
Color2  227,  227,  227
Color3  31,  31,  31
Color4  227,  227,  227
}


CostumePart ""
{
Geometry none
Texture1 none
Texture2 none
DisplayName P2371314042
RegionName Head
BodySetName standard
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry Full
Texture1 Cape_Top_01
Texture2 none
DisplayName P1987928225
RegionName Capes
BodySetName FullMantle
Color1  255,  0,  88
Color2  255,  0,  88
Color3  255,  0,  88
Color4  255,  0,  88
}


CostumePart ""
{
Geometry none
Texture1 none
Texture2 none
DisplayName P33398819
RegionName Capes
BodySetName FullMantle
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx capes/CapeLongFem.fx
Geometry none
Texture1 !X_Valkyrie_Cape_01
Texture2 !Cape_Valkyrie_01_Mask
DisplayName P4269015351
RegionName Capes
BodySetName FullMantle
Color1  255,  0,  88
Color2  255,  0,  88
Color3  255,  0,  88
Color4  255,  0,  88
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry ShortSkirt_01
Texture1 pleated
Texture2 plaid_01b
DisplayName P3930658043
RegionName "Lower Body"
BodySetName NewSkirts
Color1  255,  0,  88
Color2  255,  0,  88
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry none
Texture1 none
Texture2 none
DisplayName P1848153390
RegionName Head
BodySetName standard
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Fx none
Geometry none
Texture1 none
Texture2 none
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Geometry none
Texture1 none
Texture2 none
DisplayName P546439883
RegionName "Lower Body"
BodySetName NewSkirts
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


CostumePart ""
{
Color1  0,  0,  0
Color2  0,  0,  0
Color3  0,  0,  0
Color4  0,  0,  0
}


}

Paragon Chat seems to be advertising the hash as: Lxdw1TQl8g/dEYfAJNh7LE7oe6c=
I am calculating it as: ycY42S40EIPgvh6GaGWYI+H/tdk=

For the record, the string of data being hashed looks like this:

Code: [Select]
1f6bbaaff-0.85-0.53-0.780shortsskin_tightsminiskirt_11ff0000ff20045ccff1Tightskin_tightsTop_51ff0058ff2d40000ff2V_fem_Head.GEO/GEO_Head_V_Asym_Standard!v_sf_face_skin_head_101ff65cdff2ccca00ff3Bracer_02Skin_Bracer_02aSkin_Bracer_02b1fffd00ff2d40000ff4Hi_Heels_02skin_Hi_Heels_02askin_Hi_Heels_02b1990031ff20000ffff5standardMARTIAL_ARTS_011990031ff21f0000ff6Long_04Long_01aLong_01b1ff0000ff2aa3800ff8Tiara_01Tiara_01aTiara_01b1ccca00ff2d40000ff9TightBaseStar_102fffd4cff15FullCape_Top_011ff0058ff2ff0058ff3ff0058ff4ff0058ff17!X_Valkyrie_Cape_01!Cape_Valkyrie_01_Maskcapes/CapeLongFem.fx1ff0058ff2ff0058ff3ff0058ff4ff0058ff19ShortSkirt_01pleatedplaid_01b1ff0058ff2ff0058ff
And if you want to see what my parser is reading and writing and tossing:

Code: [Select]
Debug: adding element: BodyType with value: 1
Debug: adding element: SkinColor with value: f6bbaaff
Debug: skipping element: Scale
Debug: adding element: BoneScale with value: -0.85
Debug: skipping element: HeadScale
Debug: skipping element: ShoulderScale
Debug: adding element: ChestScale with value: -0.53
Debug: adding element: WaistScale with value: -0.78
Debug: skipping element: HipScale
Debug: skipping element: LegScale
Debug: skipping element: HeadScales
Debug: skipping element: BrowScales
Debug: skipping element: CheekScales
Debug: skipping element: ChinScales
Debug: skipping element: CraniumScales
Debug: skipping element: JawScales
Debug: skipping element: NoseScales
Debug: adding Part: 0
Debug: adding element: Geometry with value: shorts
Debug: adding element: Texture1 with value: skin_tights
Debug: adding element: Texture2 with value: miniskirt_1
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1ff0000ff
Debug: adding color: Color2 with value: 20045ccff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: adding Part: 1
Debug: adding element: Geometry with value: Tight
Debug: adding element: Texture1 with value: skin_tights
Debug: adding element: Texture2 with value: Top_5
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1ff0058ff
Debug: adding color: Color2 with value: 2d40000ff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: adding Part: 2
Debug: adding element: Geometry with value: V_fem_Head.GEO/GEO_Head_V_Asym_Standard
Debug: adding element: Texture1 with value: !v_sf_face_skin_head_10
Debug: skipping element: Texture2
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1ff65cdff
Debug: adding color: Color2 with value: 2ccca00ff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: adding Part: 3
Debug: adding element: Geometry with value: Bracer_02
Debug: adding element: Texture1 with value: Skin_Bracer_02a
Debug: adding element: Texture2 with value: Skin_Bracer_02b
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1fffd00ff
Debug: adding color: Color2 with value: 2d40000ff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: adding Part: 4
Debug: adding element: Geometry with value: Hi_Heels_02
Debug: adding element: Texture1 with value: skin_Hi_Heels_02a
Debug: adding element: Texture2 with value: skin_Hi_Heels_02b
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1990031ff
Debug: adding color: Color2 with value: 20000ffff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: adding Part: 5
Debug: adding element: Geometry with value: standard
Debug: adding element: Texture1 with value: MARTIAL_ARTS_01
Debug: skipping element: Texture2
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1990031ff
Debug: adding color: Color2 with value: 21f0000ff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: adding Part: 6
Debug: adding element: Geometry with value: Long_04
Debug: adding element: Texture1 with value: Long_01a
Debug: adding element: Texture2 with value: Long_01b
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1ff0000ff
Debug: adding color: Color2 with value: 2aa3800ff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: skipping Part: 7
Debug: adding Part: 8
Debug: adding element: Geometry with value: Tiara_01
Debug: adding element: Texture1 with value: Tiara_01a
Debug: adding element: Texture2 with value: Tiara_01b
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1ccca00ff
Debug: adding color: Color2 with value: 2d40000ff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: adding Part: 9
Debug: adding element: Geometry with value: Tight
Debug: adding element: Texture1 with value: Base
Debug: adding element: Texture2 with value: Star_10
Debug: skipping element: Fx
Debug: skipping color: Color1
Debug: adding color: Color2 with value: 2fffd4cff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: skipping Part: 10
Debug: skipping Part: 11
Debug: skipping Part: 12
Debug: skipping Part: 13
Debug: skipping Part: 14
Debug: adding Part: 15
Debug: adding element: Geometry with value: Full
Debug: adding element: Texture1 with value: Cape_Top_01
Debug: skipping element: Texture2
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1ff0058ff
Debug: adding color: Color2 with value: 2ff0058ff
Debug: adding color: Color3 with value: 3ff0058ff
Debug: adding color: Color4 with value: 4ff0058ff
Debug: skipping Part: 16
Debug: adding Part: 17
Debug: skipping element: Geometry
Debug: adding element: Texture1 with value: !X_Valkyrie_Cape_01
Debug: adding element: Texture2 with value: !Cape_Valkyrie_01_Mask
Debug: adding element: Fx with value: capes/CapeLongFem.fx
Debug: adding color: Color1 with value: 1ff0058ff
Debug: adding color: Color2 with value: 2ff0058ff
Debug: adding color: Color3 with value: 3ff0058ff
Debug: adding color: Color4 with value: 4ff0058ff
Debug: skipping Part: 18
Debug: adding Part: 19
Debug: adding element: Geometry with value: ShortSkirt_01
Debug: adding element: Texture1 with value: pleated
Debug: adding element: Texture2 with value: plaid_01b
Debug: skipping element: Fx
Debug: adding color: Color1 with value: 1ff0058ff
Debug: adding color: Color2 with value: 2ff0058ff
Debug: skipping color: Color3
Debug: skipping color: Color4
Debug: skipping Part: 20
Debug: skipping Part: 21
Debug: skipping Part: 22
Debug: skipping Part: 23
Debug: skipping Part: 24
Debug: skipping Part: 25
Debug: skipping Part: 26
Debug: skipping Part: 27

I'll keep banging on it, but Codewalker if you could pass this costume through your hash algorithm manually and tell me what your code passes to SHA1 (I know you do it incrementally but a concatenation of what you toss into the hash algorithm would be appreciated) then a diff of the two might tell me where your algorithm diverges from mine.

Edit: a horrifying thought crossed my mind: what if the internal representation of the costume is not identical to what the game saves to costume files?  All it takes is one word capitalized differently, and the hash will fail.  Ugh, I might have to implement the Iq costume request just to be able to compare the two.  If that's the case, costume files have a major fail.

Arcana

  • Sultaness of Stats
  • Elite Boss
  • *****
  • Posts: 3,672
Re: Technical side discussion
« Reply #133 on: July 17, 2015, 05:38:40 AM »
Bingo.  Irdumb.  I logged two different Paragon Chat clients into my server, so they could send each other the costumes.  This is what LA sends to VR when asked for her costume:

Code: [Select]
<costume xmlns="pc:costume"><appearance bone="-0.85" skincolor="f6bbaaff" waist="-0.78" bodytype="1" chest="-0.53"/>
<part color2="0045ccff" color1="ff0000ff" tex2="miniskirt_1" tex1="skin_tights" n="0" geom="shorts"/>
<part color2="d40000ff" color1="ff0058ff" tex2="Top_5" tex1="skin_tights" n="1" geom="Tight"/>
<part color2="ccca00ff" color1="ff65cdff" tex1="!v_sf_face_skin_head_10" n="2" geom="V_fem_Head.GEO/GEO_Head_V_Asym_Standard"/>
<part color2="d40000ff" color1="fffd00ff" tex2="Skin_Bracer_02b" tex1="Skin_Bracer_02a" n="3" geom="Bracer_02"/>
<part color2="0000ffff" color1="990031ff" tex2="skin_Hi_Heels_02b" tex1="skin_Hi_Heels_02a" n="4" geom="Hi_Heels_02"/>
<part color2="1f0000ff" color1="990031ff" tex1="MARTIAL_ARTS_01" n="5" geom="standard"/>
<part color2="aa3800ff" color1="ff0000ff" tex2="Long_01b" tex1="Long_01a" n="6" geom="Long_04"/>
<part color2="d40000ff" color1="ccca00ff" tex2="Tiara_01b" tex1="Tiara_01a" n="8" geom="Tiara_01"/>
<part color2="fffd4cff" tex2="Star_10" tex1="Base" n="9" geom="Tight"/>
<part color2="ff0058ff" color1="ff0058ff" tex1="Cape_Top_01" n="15" geom="Full"/>
<part color4="ff0058ff" color3="ff0058ff" color2="ff0058ff" color1="ff0058ff" tex2="!Cape_Valkyrie_01_Mask" tex1="!X_Valkyrie_Cape_01" fx="capes/CapeLongFem.fx" n="17"/>
<part color2="ff0058ff" color1="ff0058ff" tex2="plaid_01b" tex1="pleated" n="19" geom="ShortSkirt_01"/></costume>

Look at Part 15.  This is Part 15 in the costume file:

Code: [Select]
CostumePart ""
{
Geometry Full
Texture1 Cape_Top_01
Texture2 none
DisplayName P1987928225
RegionName Capes
BodySetName FullMantle
Color1  255,  0,  88
Color2  255,  0,  88
Color3  255,  0,  88
Color4  255,  0,  88
}

For some reason, at least at first glance, Paragon Chat is dropping Color 3 and Color 4.  It does not do that on part 17 which also has four colors in the part in the costume file, and Paragon Chat sends those:

Code: [Select]
CostumePart ""
{
Fx capes/CapeLongFem.fx
Geometry none
Texture1 !X_Valkyrie_Cape_01
Texture2 !Cape_Valkyrie_01_Mask
DisplayName P4269015351
RegionName Capes
BodySetName FullMantle
Color1  255,  0,  88
Color2  255,  0,  88
Color3  255,  0,  88
Color4  255,  0,  88
}

Not sure what's going on here.  Maybe those colors are redundant, and the costume file has them but the game doesn't actually use them, and somehow Paragon Chat has sniffed this out and drops them.  But this is a file that I resaved with I24, and it even made actual changes to the file, so this might be one of those things that needs to be hard coded around.  Help me Codewalker Kenobi, you're my only hope.

Arcana

  • Sultaness of Stats
  • Elite Boss
  • *****
  • Posts: 3,672
Re: Technical side discussion
« Reply #134 on: July 17, 2015, 07:36:48 AM »
Additional information: if I nullify (i.e. 0,0,0) those two colors in a new costume file and rehash, I end up apparently with identical information being put into the hash as Paragon Chat sends for that costume in its Iq costume response.  However, the hash still doesn't match: now I get 1XAXLw/yJTTAhxHdLHvYJKd76E4= for the hash, and the string being hashed is now:

Code: [Select]
1f6bbaaff-0.85-0.53-0.780shortsskin_tightsminiskirt_11ff0000ff20045ccff1Tightskin_tightsTop_51ff0058ff2d40000ff2V_fem_Head.GEO/GEO_Head_V_Asym_Standard!v_sf_face_skin_head_101ff65cdff2ccca00ff3Bracer_02Skin_Bracer_02aSkin_Bracer_02b1fffd00ff2d40000ff4Hi_Heels_02skin_Hi_Heels_02askin_Hi_Heels_02b1990031ff20000ffff5standardMARTIAL_ARTS_011990031ff21f0000ff6Long_04Long_01aLong_01b1ff0000ff2aa3800ff8Tiara_01Tiara_01aTiara_01b1ccca00ff2d40000ff9TightBaseStar_102fffd4cff15FullCape_Top_011ff0058ff2ff0058ff17!X_Valkyrie_Cape_01!Cape_Valkyrie_01_Maskcapes/CapeLongFem.fx1ff0058ff2ff0058ff3ff0058ff4ff0058ff19ShortSkirt_01pleatedplaid_01b1ff0058ff2ff0058ff
Something is definitely amiss: even if I force the hasher to hash the same data inside the costume Iq, I can't replicate the hash value Paragon Chat generates.  Perhaps the costume hash generation algorithm has subtly changed since Codewalker first posted it, or maybe there's a subtle difference I'm not seeing at the moment.  I'll keep looking, but something seems to be wrong with the algorithm I'm using.

Codewalker

  • Hero of the City
  • Titan Network Admin
  • Elite Boss
  • *****
  • Posts: 2,740
  • Moar Dots!
Re: Technical side discussion
« Reply #135 on: July 17, 2015, 01:41:14 PM »
I am calculating it as: ycY42S40EIPgvh6GaGWYI+H/tdk=

Hmm, I plugged your costume into Paragon Chat and ran its hash algorithm on it. Here's what I got:

Code: [Select]
----- BEGIN COSTUME DUMP -----
Hash: 2TjGyYMQNC6GHr7gI5hlaNm1/+E=
Bodytype: 1
Skin color: f6bbaaff
BoneScale: -0.85
ChestScale: -0.53
WaistScale: -0.78
Part 0
  Geom: shorts
  Tex1: skin_tights
  Tex2: miniskirt_1
  Color1: ff0000ff
  Color2: 0045ccff
Part 1
  Geom: Tight
  Tex1: skin_tights
  Tex2: Top_5
  Color1: ff0058ff
  Color2: d40000ff
Part 2
  Geom: V_fem_Head.GEO/GEO_Head_V_Asym_Standard
  Tex1: !v_sf_face_skin_head_10
  Color1: ff65cdff
  Color2: ccca00ff
Part 3
  Geom: Bracer_02
  Tex1: Skin_Bracer_02a
  Tex2: Skin_Bracer_02b
  Color1: fffd00ff
  Color2: d40000ff
Part 4
  Geom: Hi_Heels_02
  Tex1: skin_Hi_Heels_02a
  Tex2: skin_Hi_Heels_02b
  Color1: 990031ff
  Color2: 0000ffff
Part 5
  Geom: standard
  Tex1: MARTIAL_ARTS_01
  Color1: 990031ff
  Color2: 1f0000ff
Part 6
  Geom: Long_04
  Tex1: Long_01a
  Tex2: Long_01b
  Color1: ff0000ff
  Color2: aa3800ff
Part 8
  Geom: Tiara_01
  Tex1: Tiara_01a
  Tex2: Tiara_01b
  Color1: ccca00ff
  Color2: d40000ff
Part 9
  Geom: Tight
  Tex1: Base
  Tex2: Star_10
  Color2: fffd4cff
Part 15
  Geom: Full
  Tex1: Cape_Top_01
  Color1: ff0058ff
  Color2: ff0058ff
  Color3: ff0058ff
  Color4: ff0058ff
Part 17
  Tex1: !X_Valkyrie_Cape_01
  Tex2: !Cape_Valkyrie_01_Mask
  Fx: capes/CapeLongFem.fx
  Color1: ff0058ff
  Color2: ff0058ff
  Color3: ff0058ff
  Color4: ff0058ff
Part 19
  Geom: ShortSkirt_01
  Tex1: pleated
  Tex2: plaid_01b
  Color1: ff0058ff
  Color2: ff0058ff
----- END COSTUME DUMP -----

I modified the hash routine to concatenate instead of feeding to the SHA1 digest, so here is the string that is being hashed:

Code: [Select]
1f6bbaaff-0.85-0.53-0.780shortsskin_tightsminiskirt_11ff0000ff20045ccff1Tightskin_tightsTop_51ff0058ff2d40000ff2V_fem_Head.GEO/GEO_Head_V_Asym_Standard!v_sf_face_skin_head_101ff65cdff2ccca00ff3Bracer_02Skin_Bracer_02aSkin_Bracer_02b1fffd00ff2d40000ff4Hi_Heels_02skin_Hi_Heels_02askin_Hi_Heels_02b1990031ff20000ffff5standardMARTIAL_ARTS_011990031ff21f0000ff6Long_04Long_01aLong_01b1ff0000ff2aa3800ff8Tiara_01Tiara_01aTiara_01b1ccca00ff2d40000ff9TightBaseStar_102fffd4cff15FullCape_Top_011ff0058ff2ff0058ff3ff0058ff4ff0058ff17!X_Valkyrie_Cape_01!Cape_Valkyrie_01_Maskcapes/CapeLongFem.fx1ff0058ff2ff0058ff3ff0058ff4ff0058ff19ShortSkirt_01pleatedplaid_01b1ff0058ff2ff0058ff
As far as I can tell, it's identical to your results. Just for fun I took the concatenated string that you posted and ran it through sha1 and base64 on the command line. I got this.

c9c638d92e341083e0be1e8668659823e1ffb5d9

And ah dammit I got the endianness backwards.

ycY42S40EIPgvh6GaGWYI+H/tdk=
c9c638d92e341083e0be1e8668659823e1ffb5d9
2TjGyYMQNC6GHr7gI5hlaNm1/+E=
d938c6c98310342e861ebee023986568d9b5ffe1

I had been byte swapping OpenSSL's output because I was putting it in 32-bit unsigned ints and using sprintf with %08x to create the original hash, which was a hex string. When I switched to Base64 I forgot to undo the byte swapping. Well, I can't fix it right now without making everybody invisible, but I'm planning to do a "v2" costume hash for 1.0 to fix some nits (mostly making the hashing of the floating point scales less ambiguous and dependent on truncation/printf semantics), so I'll fix it then. 0.98 is going to include some compatibility code to ease the transition when we get to 1.0.
« Last Edit: July 18, 2015, 12:50:31 AM by Codewalker »

Codewalker

  • Hero of the City
  • Titan Network Admin
  • Elite Boss
  • *****
  • Posts: 2,740
  • Moar Dots!
Re: Technical side discussion
« Reply #136 on: July 17, 2015, 01:49:48 PM »
Edit: a horrifying thought crossed my mind: what if the internal representation of the costume is not identical to what the game saves to costume files?  All it takes is one word capitalized differently, and the hash will fail.  Ugh, I might have to implement the Iq costume request just to be able to compare the two.  If that's the case, costume files have a major fail.

This shouldn't be an issue in practice. Yes, the game client will modify a .costume file that you load in odd ways before sending it to the server. But your bot can send the costume file as-is, and so long as the hash matches what you sent, Paragon Chat will accept it. It doesn't matter if the client mangles it once it's been transmitted because PC doesn't care what the client thinks at that point.

It just means that a costume file loaded by the game and a costume file loaded by your bot will have slightly different contents (and hashes). But so long as everything is consistent it will still work, you'll just have two very slightly different costumes that look identical.

Question: since hashing is case sensitive, is case forced all up or all lower for consistency?  The sample suggested it was not ("Leather" retained its capitalization).  Also, this probably has nothing to do with anything, but I24 itself *created* a Part that was not in the I23 costume file, and weirdly its not a functional part entry: it looks like this in Part 25:

Along the same lines, the client likes to swap those back and forth all the time. It's kind of annoying but doesn't affect the hashing. While the client happily considers them equivalent, Paragon Chat does not and is very careful to preserve case between the database and the wire. So the costume with "leather" will have a different hash than the one with "Leather", but once PC has them they will both work and won't randomly change.

Forcing them to lowercase for hashing would make the hashes equivalent and be slightly more efficient if two people are using the same costume differing only by case. That shouldn't really happen, but it's still a bit cleaner to ignore case for costume parts, so I'll do that in the V2 hash.

slickriptide

  • Elite Boss
  • *****
  • Posts: 356
Re: Technical side discussion
« Reply #137 on: July 17, 2015, 05:13:17 PM »
Out of curiosity:

How resilient is the costume protocol? There are a lot of places where the informal spec (i.e., page two of this thread) says "may omit" in regards to essentially null data, like parts that have a color but no geometry or fx. What happens if I decide to send those anyway? Just extra clutter on the wire?

What if I send all 30 parts from the costume file? Does it care about the extra two that the game's costume handler apparently never got around to defining?

What if I attach some extra attributes to a costume stanza, or inject some XML into it because, reasons...? I want to send a costume-related payload to another bot or app or something and I want to broadcast it rather than target the recipient and I'm too lazy to write my own special protocol to handle my data. Will Paragon Chat reject it or will it ignore the stuff it doesn't care about?

Note that I have no current intentions to do any of the above things, though the answer might affect how I handle this idea I'm experimenting with of saving costume stanzas to permanent storage and using them in various ways.


Codewalker

  • Hero of the City
  • Titan Network Admin
  • Elite Boss
  • *****
  • Posts: 2,740
  • Moar Dots!
Re: Technical side discussion
« Reply #138 on: July 17, 2015, 05:35:51 PM »
What happens if I decide to send those anyway? Just extra clutter on the wire?

Exactly. It will still set it to 0, but it was 0 anyway since that is the default that it's initialized to.

What if I send all 30 parts from the costume file? Does it care about the extra two that the game's costume handler apparently never got around to defining?

It will faithfully pass them on to the game client. I have no idea how the client handles it. I suspect you can put extra pieces there and they would work fine, but be uneditable in the tailor. It won't pass more than 30 though, that's the absolute max and anything beyond n="29" is discarded.

What if I attach some extra attributes to a costume stanza, or inject some XML into it because, reasons...? I want to send a costume-related payload to another bot or app or something and I want to broadcast it rather than target the recipient and I'm too lazy to write my own special protocol to handle my data. Will Paragon Chat reject it or will it ignore the stuff it doesn't care about?

Per XML handling best practices, it will silently ignore any attributes or elements that it doesn't know about.

Paragon Chat ignores any 'broadcasted' costumes in any event; it only parses them if they are sent in direct response to one of its costume IQ queries (i.e. has an id on the iq that matches a query it is waiting for a response to).

Arcana

  • Sultaness of Stats
  • Elite Boss
  • *****
  • Posts: 3,672
Re: Technical side discussion
« Reply #139 on: July 17, 2015, 05:58:51 PM »
As far as I can tell, it's identical to your results.

Is it possible there's a string escape bug in the costume code?  There appears to be a single character difference between what I have and what you have, and its a forward slash.  The Fx in Part 17 is "capes/CapeLongFem.fx" and it shows up that way in my parser, and also in your parser, but your hash string shows it as "capesCapeLongFem.fx."  Is that just a glitch in the hash string printing, or is that also happening in the incremental SHA hash as well?