• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

gcio - Jass2 input/output with the gamecache natives

Status
Not open for further replies.
Level 13
Joined
Nov 7, 2014
Messages
571
gcio - Jass2 input/output with the gamecache natives

The gamecache natives allow for reading (ReloadGameCachesFromDisk) and writing (SaveGameCache) of data
from/to the disk (the Campaigns.w3v file), in single player only!

Input data can be passed to a running map by writing to the Campaigns.w3v file from an external program, then
from the jass' side, first the ReloadGameCachesFromDisk native must be called in order to reload the data from
the campaigns file and then the other gamecache natives (GetStoredInteger|Real|String) would be used to get
the data.

Output data can be written with jass using the StoreString and SaveGameCache natives. The SaveGameCache
native writes the data to the campaigns file, then an external program can extract the data and write it to another file.

Gamecaches were used by Blizzard to store progress of campaigns that spanned several maps.
Each single player profile has its own Campaigns.w3v file.
The location of the file is (at least on windows):

/path/to/game/save/ProfileX/Campaigns.w3v -- for game versions prior to 1.28 (I think)
/users/<user-name>/Saved Games/Warcraft III/ProfileX/Campaigns.w3v -- for later game versions

The 'X' in 'ProfileX' is an integer number that starts from 1, if you have 3 profiles then there would be 3 directories
called Profile1, Profile2 and Profile3.

WARNING:
It is possible that gcio could corrupt your profile's Campaigns.w3v file which would result in campaigns progress loss!
So before any attempts of using gcio you should either make a backup of your profile's Campaigns.w3v file
or create a new profile with an empty Campaigns.w3v file, which could be deleted if something goes wrong.



Feeding data to your map script using gcio:

gcio monitors a directory (given as a command line argument) that contains files with a .gc-txt extension.
Each .gc-txt file represents a single gamecache. The format of a .gc-txt file can hopefully be inferred from this example:

Lua:
-- this is a comment
-- if your editor supports Lua highlighting, you might want to use it

'gamecache name':

-- what follows are the keys of the gamecache

'key-1':
    -- each key has 3 lists, list of integers, list of reals and list of strings
    integers
        -- each list consists of a name-value pair
        'a': 1 -- name is 'a', integer value is 1
        'b': 2
    reals
        'tau': 6.28 -- name is 'tau', value is 6.28
    strings
        'foo': 'bar' -- name is 'foo', value is 'bar'
        'baz': 'qux'

 -- if we don't use an end token here, 'key-2' would be parsed as the name
 -- of the next string after 'baz': 'qux'
end

'key-2':
    -- the order is always integers, reals, strings
    -- so seeing integers after reals is an error
    reals
        'tau': 6.28

    -- integers
    --     'a': 1
    --     'b': 2
end

'key-3':
    integers
        'a': 1
    -- it is allowed to skip any of the lists (integers, reals, strings)
    -- here we are skipping the list of reals
    strings
        'foo': 'bar'
end

'key-4':
    -- no integers, nor reals nor strings
end

'key-5':
    integers -- empty integers list
    reals
        'tau': 6.28
    strings -- empty strings list
end

'key-6':
    -- newlines are required

    -- integers reals 'tau': 6.28 strings -- error

    -- ok
    integers
    reals
        'tau': 6.28
    strings
end

'key-7':
    strings
        -- a literal single quote can be used in a string
        -- by escaping it with a backslash
        'foo': 'foo\'s bar'

        -- a backslash not followed by a single quote is a literal backslash
        'path': 'to\file'
end

'key-8':
    -- hexadecimal notation is supported for integers and reals
    integers
        'integer-255': 0xFF
    reals
        'real-1.0': 0x3F800000
end

'key-9':
    strings
    'multiline-string': '
Line A
Line B
'
end

A change to a .gc-txt file is picked up by gcio and the target campaigns file (passed as a command line argument) is updated.

From jass' side the change to the campaigns file cannot be automatically detected (as far as I know), so the reload
of the campaigns file must be triggered manually.

Given the following gamecache stored in the my-tweak-files/gc1.gc-txt file:
Lua:
'gc1':
'key-name':
    integers
        'integer-name': 1
    reals
        'real-name': 2.0
    strings
        'string-name': 'three'
end

We start gcio with the command:
gcio -in my-tweak-files path/to/my/profile/Campaigns.w3v

Then we trigger a change to the gc1.gc-txt file (add a newline or whitespace somewhere, or something...),
gcio picks up the change and updates the campaign file.

Then from jass we can use the values with:
JASS:
function gc1_example takes nothing returns nothing
    local gamecache gc1 = InitGameCache("gc1")
    if not ReloadGameCachesFromDisk() then
        call BJDebugMsg("could not reload gamecaches from disk")
        return
    endif
    call BJDebugMsg(I2S( GetStoredInteger(gc1, "key-name", "integer-name") )) // 1
    call BJDebugMsg(R2S( GetStoredReal(gc1, "key-name", "real-name") )) // 2.0
    call BJDebugMsg(( GetStoredString(gc1, "key-name", "string-name") )) // three
endfunction



Dumping data from jass to a file using gcio:

gcio monitors the campaigns file for changes, when these happen it goes over all the gamecaches stored in the
campaigns file and looks for gamecache names starting with '>', then it uses whatever is after the '>' as the
name of the output file. The content of that file comes from the values of the string-list of the key with
a name = 'lines'.

We start gcio with the command:
gcio -out out-files path/to/my/profile/Campaigns.w3v

Then from jass we write some data:
JASS:
function gc_out_example takes nothing returns nothing
    local gamecache gc = InitGameCache(">my-random-numbers.txt")
    local integer i = 1
    loop
        exitwhen i > 10

        call StoreString(gc, "lines", I2S(i), R2S(GetRandomReal(0.0, 1.0)))

        set i = i + 1
    endloop
    call SaveGameCache(gc) // flush the gamecache to disk
endfunction

After calling the gc_out_example function the out-files/my-random-numbers.txt file should exist and contain some
random numbers, say:
JASS:
0.057
0.248
0.263
0.306
0.959
0.861
0.676
0.327
0.501
0.224



The gamecache natives are a bit wordy so you might want to write some convinience wrappers, for example:

JASS:
struct Gc_Key
    gamecache gc
    string key_name
    string val_name

    static method create takes gamecache gc, string key_name returns Gc_Key
        local Gc_Key gk = allocate()
        set gk.gc = gc
        set gk.key_name = key_name
        return gk
    endmethod

    method operator [] takes string val_name returns Gc_Key
        set this.val_name = val_name
        return this
    endmethod

    method operator i takes nothing returns integer
        return GetStoredInteger(this.gc, this.key_name, this.val_name)
    endmethod
    method operator i= takes integer x returns nothing
        call StoreInteger(this.gc, this.key_name, this.val_name, x)
    endmethod

    method operator r takes nothing returns real
        return GetStoredReal(this.gc, this.key_name, this.val_name)
    endmethod
    method operator r= takes real x returns nothing
        call StoreReal(this.gc, this.key_name, this.val_name, x)
    endmethod

    method operator s takes nothing returns string
        return GetStoredString(this.gc, this.key_name, this.val_name)
    endmethod
    method operator s= takes string x returns nothing
        call StoreString(this.gc, this.key_name, this.val_name, x)
    endmethod
endstruct

for getting input values and:

JASS:
struct Out_File
    gamecache gc
    integer line = 1
    string cur_line = ""

    static method open takes string name returns Out_File
        local Out_File f = allocate()
        set f.gc = InitGameCache(">" + name)
        call FlushStoredMission(f.gc, "lines")
        return f
    endmethod

    method write takes string s returns nothing
        set this.cur_line = this.cur_line + s
    endmethod

    method writeln takes string s returns nothing
        call StoreString(this.gc, "lines", I2S(this.line), this.cur_line + s)
        set this.line = this.line + 1
        set this.cur_line = ""
    endmethod

    method close takes nothing returns nothing
        call SaveGameCache(this.gc)
        call this.destroy()
    endmethod
endstruct

for writing out data to a file.


Credits:
PitzerMiker, whose CacheConv tool more or less documents the w3v file format, and everyone else that helped figuring it out.
 

Attachments

  • gcio.zip
    44 KB · Views: 56
  • Like
Reactions: pyf
Splendid idea. By looking at the gist of it, you can make progress through the game without actually progressing through it just by writing the necessary data (if plausible) (in my opinion).

I wonder why a prefix ">" is needed for initializing game cache in the script.

Can this somehow allow the reading of one's game-cache data in multiplayer?
 
Status
Not open for further replies.
Top