npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

uesavetool

v1.1.7

Published

A Node.js implementation for deserializing and converting GVAS/.sav files to JSON and vice-versa.

Downloads

47

Readme

UESaveTool

A Node.js implementation for deserializing and converting GVAS/.sav files to JSON and vice-versa.

npm version npm license

Usage

You must have Node.js Version 16.7.0+ installed.
Download/Clone this entire repo by clicking on the Code button above.

Convert Between .sav and .json

node ./usavetool.js [mode: -sav-to-json, -json-to-sav] [input path] [output path?]

Pipe output to another script

node ./uesavetool.js [mode] [input] | my-script

Adding to your project

First things first:

npm install uesavetool

Also set type as module in package.json. Only ESModules are supported currently.

{
  "type": "module"
}

Using Gvas class for deserialization

import * as fs from 'fs'
import { Gvas, Serializer } from 'uesavetool'

fs.readFile(sav-path, (err, buf) => {
    if(err) throw err;

    const gvas = new Gvas();
    const serial = new Serializer(buf);
    gvas.deserialize(serial);
    
    // manipulate gvas here

    fs.writeFile(json-path, JSON.stringify(gvas), (err) => {
        if(err) throw err;
    })
})

Using Gvas class for serialization

import * as fs from 'fs';
import { Gvas } from 'uesavetool';

fs.readFile(json-path, 'utf8', (err, data) => {
    if(err) throw err;

    const gvas = Gvas.from(JSON.parse(data));

    // manipulate gvas here

    fs.writeFile(sav-path, gvas.serialize(), (err) => {
        if(err) throw err;
    })
})

Implementation Notes

If you want to expand functionality of this tool, you should follow the design patterns implemented within:

  • All Properties extend from the Property class which has 4 functions: get Size(),deserialize(), serialize(), and static from()
  • Properties are in charge of serializing/deserializing themselves due to the unique formatting for each property
  • Use the Serializer class to read/write data on it's internal Buffer
  • Never instantiate a Property with new outside of a constructor or it's own from function, instead, you should use the PropertyFactory.create() static function, which passes an object argument to the from() function implemented within the specified property type
  • For integration in other projects, you should use the Gvas class, which has 4 functions: get Size(), deserialize(), serialize(), and static from()
  • deserialize() accepts a Serializer as an argument, and returns the Gvas instance
  • Properties' names must be exactly the same as in the .sav
  • Array properties are implementation dependent due to unique serialization
  • Size calcualated by get Size() is NOT written to the serialized data buffer. That value is usually, but not always, the total size of the property/properties within the current Property.

Adding a new Property type

AnotherPropery.js

import { 
    Property,
    PropertyFactory,
    Serializer
} from 'uesavetool'

export class AnotherProperty extends Property {
    constructor() {
        super();
        // Attributes specific to this property type
        // Use this.Property for it's value(s)
    }
    get Size() {
        // Calculate number of bytes for serialization. Including this.Name and this.Type
        // Each string attribute will have a 4-byte size followed by that actual string
        let size = this.Name + 4;
        size += this.Type + 4;
        // this.Property may be another `Property` with it's own `Size` getter
        return size;
    }
    deserialize(serial, size) {
        // Serial is a `Serialzer` to make this easier
        // If `size` is passed, use `serial.tell < (start_offset + size)` as a loop condition
        // Do not deserialize this.Name, this.Type or the serialized Size here.
        // This function is called from the parent Property which already deserializes them
        // The Size that is deserialized here is not the same as this.Size
        return this;
    }
    serialize() {
        let serial = Serializer.alloc(this.Size);
        serial.writeString(this.Name);
        serial.writeString(this.Type);
        serial.writeInt32(/* this.Property size in bytes */)
        serial.seek(/* padding length */)
        // serialize this.Property
        return serial.Data;
    }
    static from(obj) {
        let prop = new AnotherProperty();
        prop.Name = obj.Name
        prop.Type = obj.Type
        if(obj.Property) {
            // If this.Property is a value
            prop.Property = obj.Property

            // If this.Property is a `Property`
            prop.Property = PropertyFactory.create(obj.Property);
        }
        return prop;
    }
}

index.js

import { PropertyFactory } from 'uesavetool'
import { AnotherProperty } from './AnotherProperty.js'

PropertyFactory.Properties['AnotherProperty'] = AnotherProperty;

export { AnotherProperty }

Adding a new Array type

Essentially the same as adding a new Property type, but since ArrayProperty.StoredPropertyType will be a normal Property name, adding to PropertyFactory is different. Also Size will ONLY include the size of it's properties.

index.js

import { PropertyFactory } from 'uesavetool'
import { AnotherProperty } from './AnotherProperty.js'
import { AnotherPropertyArray } from './AnotherPropertyArray.js'

PropertyFactory.Properties['AnotherPropertyArray'] = AnotherPropertyArray //Needed if `Type` string ends with "Array" after being stored
PropertyFactory.Arrays['AnotherProperty'] = AnotherPropertyArray //

export { AnotherPropertyArray }

Notes on PropertyFactory.js

PropertyFactory will automatically trim null-terminating characters from strings. The name of the Property type will be in the .sav in utf8

Anotomy of a Property in a GVAS

Sizes are in Little-Endian, so the first byte read is the least significant. Strings are null-terminating. The following example is an StrProperty type.

| | Bytes | Value |:----------------------|:------------------------------------------|:------------------ | Name Size | 0D 00 00 00 | 13 bytes | Name | 53 61 76 65 53 6C 6F 74 4E 61 6D 65 00 | "SaveSlotName\0" | Type Size | 0C 00 00 00 | 12 bytes | Type | 53 74 72 50 72 6F 70 65 72 74 79 00 | "StrProperty\0" | Property Size | 0E 00 00 00 | 14 bytes | Padding | 00 00 00 00 00 | 5 null characters | Property Value Size | 0A 00 00 00 | 10 bytes | Property Value | 47 61 6D 65 53 74 61 74 65 00 | "GameState\0"

Padding is different for most properties. Some properties contain a StoredPropertyType value, such as the StructProperty and ArrayProperty. The Tuple does not exist in a GVAS, instead, Tuple was written to encapsulate lists of properties within the StructProperty and Gvas.Properties. A Tuple is used whenever the string None appears within the file and marks the end of a list of properties.

Disclaimer

THIS SCRIPT WAS BUILT FOR THE BPM: BULLETS PER MINUTE COMMUNITY, BUT THIS MAY PROVE USEFUL FOR OTHER UE-BASED GAMES. IT IS NOT GUARANTEED TO WORK FOR ALL UE GAME SAVES.

Verification Tools

HxD Freeware Hex Editor

https://mh-nexus.de/en/hxd/

Notepad++

https://notepad-plus-plus.org/

Credits

GVAS Converter

https://github.com/13xforever/gvas-converter

UeSaveSerializer

https://gist.github.com/Rob7045713/2f838ad66237f87c86d5396af573b71c