Garzhad wrote:How did you make all these edits to the weapons in the first place?.
tl;dr - i'm very modest, awesome and smart, but i can't help you
First, i didn't make these edits, the original author of this mod did. My task was simpler - replicate these same changes on newer version of the file, the one from Patch4.
Obvious solution was to just take the Patch2 version, take the modded version, compare them in a hex editor, then somehow record all the changes and apply them to the Patch4 version. That didn't work out, probably because i've used Patch3 version for comparison (and it had tons of unrelated changes - as i've said, this file contains more than just item stats).
Next obvious solution: unpack the files (assuming that they are like archives and contains individual resource files inside), find the files for weapons and compare *that* between versions. I've tried using Disunity as unpacker, but resulting binary files still sported too much differences, and i just wasn't sure what i'm seeing. Non-binary dumping that Disunity offered turned out to be not very great and missed the relevant files completely.
Then i thought about just finding relevant parts of the big resource file by names and descriptions, then inferring the data layout from that (since weapon stats are known, both for un-modded and for modded versions). Didn't go far in that direction, too much uncertainty there (names and descriptions are easy to find, but i couldn't be sure where one item ends and another begins). I clearly needed to work with unpacked resources after all.
At that point i started to really dislike Disunity. I did remember reading somewhere (on this forum probably) that existing packers/unpackers were incapable of building valid resource bundles, which meant that they are useless for modding. I didn't verify these claims, just decided to write my own unpacker. Thankfully, Disunity source code was descriptive enough. After reading it a bit and writing partial implementation in Python i realized that i can simplify things a lot by going the Unix way, and i did.
Unlike Disunity, which aims to work with bundles and assets in one go (author plans to release a version with a GUI next, this approach will definitely work for that purpose), my unpacker is split into parts, each part does one thing, but does it right.
First part unpacks bundles, and gives you a directory with assets files, much like Disunity. Then i wrote its counterpart that *packs* these files back (toyed with the idea about adding extra files, but eventually discarded it - difficult to do when all you have to work with are file/directory structure and pickled objects). After some tinkering and fixes my bundle packer/unpacker was able to unpack the bundle in question and then pack it back together the way it was, bit-identical.
Then i wrote unpacker for assets files (turned out, the WL2 bundle that i was interested in contained just one big file, so getting was not a big achievement). All assets are identified by an ID (a number), and have class and type metadata known about them. Assets file may also contain information about classes in its header (Disunity source seems to indicate that this is not a rule, and provides functionality for getting class info from various sources and assembling it in a separate database that is later consulted with) - WL2 one certainly does. Anyway, the unpacker creates a directory structure, where first level directories are class IDs (later i updated it to include class IDs *and* class names), their child directories are type IDs, and inside them are objects of that class and type. Initially objects were just named by their ID, i thought it to be enough. After that i wrote a packer that reversed the process, and verified that it creates bit-identical assets file.
Well, that was that - i had separate small binary files, each describing just one object. Just needed to find which ones described weapons, then see what changed in *these* files between versions - and then i'd be able to just patch these files only in unpacked Patch4 version (or even simpler - just put them as-is into unpacked Patch4 version), then pack it back, and voila!
Sadly, that didn't work either. Turns out, object IDs change between versions, and while ID changes were small (say, 12576 instead of 12580), i couldn't rely on them. Grepping files for identifying strings was a dead-end too, since, as it turned out, the game has, like, 10 different weapons described as "Claws", for example.
Then i whipped up a program that dumps aforementioned class info in text form, and tried to use that info to parse object files to figure out if they contain any persistent identifiers that i could use. That took longer than i thought - Unity objects combine primitive types in a tree-like structure to create complex datatypes (kind of like Matroska tag system), so a 'string' is actually an 'Array', which is actually a 'size' (length) and a "length" number of 'char's. Dictionaries are even weirder. Also, you have to respect the alignment. Luckily, Disunity source code does have descriptive commends about that, so i was able to figure it out, and discovered that these files do not contain any identifying information. Bummer (they do have an m_Name string field, it's consistently empty).
Then i scoured the whole assets file for that info, correctly inferring that object id must also be recorded *somewhere* and paired with some identifying information, otherwise the game wouldn't know what to look for. That part i was able to solve only partially. First, there's a single (and relatively large) object of class 142 (called AssetBundle) type 142, and it contains, among other things, a field named m_Container of type "map", which is an array of pairs, each pair contists of a string and an AssetInfo. AssetInfo is a struct with some preload indices and, most importantly, a PPtr<Object>, which, it seems, contains an object ID. The aforementioned string seems to be a name of some sort. I wrote a program that loads up this map and then goes through the unpacked objects and renames them, so that instead of raw and changing-between-versions numbers i'd have names, at which point i'd be able to go back to my original plan.
That didn't exactly work out. First, because these "names" turned out to be highly non-unique (there are, like, 300 objects of various classes and types with the name "human"). Obviously, you can't have 300 files with the same name...Second - because that map was *really* incomplete, missing info on *lot* of objects.
I dug further. There was a big swath of objects of class 1 (GameObject) type 1 (by the way, Unity has this weird system where positive class IDs and type IDs are the same, but for class 114 (MonoBehaviour; Unity authors must have been brits) type ids are not the same - in fact, they are negative, and identify extra classes) - they are small and contain very conspicuous strings that *could* be the names i wanted, and they seem to contain IDs of objects too. So i wrote extra code that reads first few fields of an object and, if it had a m_GameObject field, uses the ID from that field to read *another* object (if it exists) in that big list of GameObject objects, and reads the name of the object there. That worked much better: first, all names that i've got from this seemed to have been unique; second, they did identify all the objects that i needed identified - objects of class 114 (MonoBehaviour) and types -38, -39, -40 and -41 (melee weapons, guns, shotguns and energy weapons, it seems; h-m-m-m...i probably should have labeled types as well). Since some names were still from that map i found first, and thus non-unique, i decided to keep object IDs in the names.
At this point i was able to write a simple program that just walked through one directory tree (unpacked original Patch2 resource), took each file and compared it to its counterpart in another directory tree (unpacked modded Patch2 resource) and checked if the file changed. If it did, the program would write change info (kind of like 'diff' does - offsets and bytes to change, but without context) into stdout.
Then i wrote a program (kind of like 'patch') that applies that reads this change info and re-produces the changes on a different directory tree (unpacked original Patch4 resource).
At which point i just packed the now-patched Patch4 resource back into an assets file, then packed that into a bundle, and this is what you can download now. Took me a few days, but now i can re-produce the same changes on a Patch5, if (when?) it ever comes out, and it will only take a few commands (though some of them do run for minutes, especially the one that renames object files).
Hopefully, that answered your question and any other questions you might have had.
Before you ask for the programs, i must warn you that they all have command line interface only, the code is gnarly, the diff-patch program pair only works for byte tweaks (if object file size is different (which will happen if you edit strings or add/remove firing modes or tags), the diff program refuses to consider it - changed object size means re-calculating offsets for assets file header, which i might have gotten wrong), ignores new objects (you can't add objects - again, that requires header modifications that i haven't coded in, and i would have liked to preserve the order in which objects are stored - to make clean and minimal diffs, which means that object list must be stored in human-editable form, and you need to put objects into it and ...well, you see where it's going) and is baffled by deleted objects (same reason). Also, nothing here helps you *make* new changes (as i only worked with already modded files) - again, i haven't coded any user interface, nor am i going to; parsing an object file into human-editable text form is complicated, because of the complex type system that needs to be folded into things like lists, strings and dictionaries, and then *unfolded* when you write the object back; besides, for weapon balancing you'd probably want to put multiple items into some kind of table, which is not doable with simple text files (you'd need CSV at the least, but then how are you going to represent arrays of different length...?). And my object-reading code is imperfect (it crashes on some objects; this is why the labeling program only reads first few fields of an object - to avoid reading field trees it can't handle).