• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • Create a faction for Warcraft 3 and enter Hive's 19th Techtree Contest: Co-Op Commanders! Click here to enter!
  • Get your art tools and paintbrushes ready and enter Hive's 34th Texturing Contest: Void! Click here to enter!

Stable Lua FileIO

This bundle is marked as pending. It has not been reviewed by a staff member yet.
  • Like
Reactions: deepstrasz
This is an update to the FileIO library, originally created by @Trokkin. It allows you to write strings into text files in the CustomMapData subfolder in the Warcraft 3 directory, then read from those text files in a later game. For documentation, see the original thread.

While working with FileIO, I experienced crashes from loading certain files, so I set out to investigate and fix them.

The Lua FileIO works by enclosing every string segment, which has a maximum length of 255, in long string delimiters [[ and ]]. This can get messed up in several ways:
  • If ]] appears inside the string, it prematurely closes the string and the game crashes upon loading the file. Therefore, each ] is replaced by !].
  • Several characters are escaped with a backslash when written to the text file. This will increase the size of the string by 1 for each of those characters, which, in turn, can make it exceed the string size limit of 255, cutting off the closing delimiter, and therefore crashing the game when trying to load the file. Therefore, this version counts the number of characters that need escaping and reduces the string size accordingly.
  • Some issues regarding \x25 and \\\\ that I don't fully understand, which I fixed through experimentation.
In addition, I removed the OnInit wrapper because it is possible to use the Save function in the Lua root without problems. Bear in mind, though, that using FileIO.Load from within the Lua root will cause the game to crash.

Lua:
if Debug then Debug.beginFile "FileIO" end
--[[
    FileIO v2.0

    Author: Trokkin
    Updated by Antares

    Provides functionality to read and write files, optimized with lua functionality in mind.

    API:

        FileIO.Save(filename, data)
            - Write string data to a file

        FileIO.Load(filename) -> string?
            - Read string data from a file. Returns nil if file doesn't exist.

        FileIO.SaveAsserted(filename, data, onFail?) -> bool
            - Saves the file and checks that it was saved successfully.
              If it fails, passes (filename, data, loadResult) to onFail.

    Optional requirements:
        DebugUtils by Eikonium                          @ https://www.hiveworkshop.com/threads/330758/

    Inspired by:
        - TriggerHappy's Codeless Save and Load         @ https://www.hiveworkshop.com/threads/278664/
        - ScrewTheTrees's Codeless Save/Sync concept    @ https://www.hiveworkshop.com/threads/325749/
        - Luashine's LUA variant of TH's FileIO         @ https://www.hiveworkshop.com/threads/307568/post-3519040
        - HerlySQR's LUA variant of TH's Save/Load      @ https://www.hiveworkshop.com/threads/331536/post-3565884

    Updated: 15 June 2025
--]]
do
    local RAW_PREFIX = ']]i([['
    local RAW_SUFFIX = ']])--[['
    local RAW_SIZE = 255 - #RAW_PREFIX - #RAW_SUFFIX
    local LOAD_ABILITY = FourCC('ANdc')
    local LOAD_EMPTY_KEY = '!@#$, empty data'

    local name

    local function open(filename)
        name = filename
        PreloadGenClear()
        Preload('")\nendfunction\n//!beginusercode\nlocal p={} local i=function(s) table.insert(p,s) end--[[')
    end

    local function write(s)
        --I have no idea why this does anything, but it is required.
        s = s:gsub("x25", "\\x25")
        --Avoid ]] in string.
        s = s:gsub("\x25]", "!]")
        --Avoid [[ in string.
        s = s:gsub("\x25[", "![")
        --Avoid doubling backslashes.
        s = s:gsub("\\\\", "\\")
        local i = 1
        local size = #s
        local pos, __, num, str
        repeat
            pos = i + RAW_SIZE - 1
            str = s:sub(i, pos)
            --Escaped characters increase preload string size in file, causing the suffix to get swallowed, which in turn causes a crash.
            __, num = str:gsub("[\\\n\'\"]", "")
            pos = pos - num
            --A ] at the end of a preload string will crash the game. A newline at the beginning of a preload string will be swallowed.
            while s:sub(pos, pos) == "]" or s:sub(pos + 1, pos + 1) == "\n" do
                pos = pos - 1
            end
            Preload(RAW_PREFIX .. s:sub(i, pos) .. RAW_SUFFIX)
            i = pos + 1
        until i > size
    end

    local function close()
        Preload(']]BlzSetAbilityTooltip(' ..
            LOAD_ABILITY .. ', table.concat(p), 0)\n//!endusercode\nfunction a takes nothing returns nothing\n//')
        PreloadGenEnd(name)
        name = nil
    end

    ---@param filename string
    ---@param data string
    local function savefile(filename, data)
        open(filename)
        write(data)
        close()
    end

    ---@param filename string
    ---@return string?
    local function loadfile(filename)
        local s = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
        BlzSetAbilityTooltip(LOAD_ABILITY, LOAD_EMPTY_KEY, 0)
        Preloader(filename)
        local loaded = BlzGetAbilityTooltip(LOAD_ABILITY, 0)
        BlzSetAbilityTooltip(LOAD_ABILITY, s, 0)
        if loaded == LOAD_EMPTY_KEY then
            return nil
        end
        ---@diagnostic disable-next-line: redundant-return-value
        return loaded:gsub("!\x25[", "["):gsub("!\x25]", "]"):gsub("\\\\x25", "\x25\x25"):gsub("\\n", "\n")
    end

    ---@param filename string
    ---@param data string
    ---@param onFail function?
    ---@return boolean
    local function saveAsserted(filename, data, onFail)
        savefile(filename, data)
        local res = loadfile(filename)
        if res == data then
            return true
        end
        if onFail then
            onFail(filename, data, res)
        end
        return false
    end

    FileIO = {
        Save = savefile,
        Load = loadfile,
        SaveAsserted = saveAsserted
    }
end
Contents

Stable Lua FileIO (Binary)

Top