2018-12-14 19:51:03 +00:00
|
|
|
local C = Class:extend()
|
|
|
|
|
2018-12-14 21:57:56 +00:00
|
|
|
-- https://www.lua.org/manual/5.1/manual.html#5.7
|
|
|
|
-- w+: Update mode, all previous data is erased;
|
|
|
|
-- b: Binary mode, forces Windows to save with Unix endings.
|
|
|
|
MODE_WRITE_ERASE_EXISTING = 'w+b'
|
|
|
|
|
|
|
|
local ITEM_DATA = require 'database.items'
|
2018-12-14 19:51:03 +00:00
|
|
|
|
|
|
|
function C:new(path)
|
|
|
|
logInfo('reading TSC: ' .. path)
|
|
|
|
|
|
|
|
local file = lf.newFile(path)
|
|
|
|
assert(file:open('r'))
|
|
|
|
|
|
|
|
local contents, size = file:read()
|
|
|
|
self._text = self:_codec(contents, 'decode')
|
|
|
|
|
|
|
|
assert(file:close())
|
|
|
|
assert(file:release())
|
2018-12-14 21:57:56 +00:00
|
|
|
|
|
|
|
-- Determine set of items which can be replaced later.
|
|
|
|
self._unreplaced = {}
|
|
|
|
self._mapName = path:match("^.+/(.+)$")
|
|
|
|
for k, v in pairs(ITEM_DATA) do repeat
|
|
|
|
if (v.map .. '.tsc') ~= self._mapName then
|
|
|
|
break -- continue
|
|
|
|
end
|
|
|
|
local item = _.clone(v)
|
|
|
|
table.insert(self._unreplaced, item)
|
|
|
|
until true end
|
|
|
|
self._unreplaced = _.shuffle(self._unreplaced)
|
|
|
|
end
|
|
|
|
|
|
|
|
function C:hasUnreplacedItems()
|
|
|
|
return #self._unreplaced >= 1
|
|
|
|
end
|
|
|
|
|
2018-12-15 04:22:03 +00:00
|
|
|
function C:replaceItem(replacement)
|
|
|
|
assert(self:hasUnreplacedItems())
|
|
|
|
local original = table.remove(self._unreplaced)
|
|
|
|
|
|
|
|
local template = "[%s] %s -> %s"
|
|
|
|
logNotice(template:format(self._mapName, original.name, replacement.name))
|
|
|
|
|
2018-12-19 05:27:33 +00:00
|
|
|
if original.replaceBefore then
|
|
|
|
for needle, replacement in pairs(original.replaceBefore) do
|
|
|
|
self._text = self:_stringReplace(self._text, needle, replacement)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-12-15 04:22:03 +00:00
|
|
|
-- Erase first, in case replace attribute would place some text that would match here...
|
|
|
|
if original.erase then
|
|
|
|
local erases = original.erase
|
|
|
|
if type(erases) == 'string' then
|
|
|
|
erases = {erases}
|
|
|
|
end
|
|
|
|
for _, erase in ipairs(erases) do
|
|
|
|
self._text = self:_stringReplace(self._text, erase, '')
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
self:_replaceAttribute(original, replacement, 'command')
|
|
|
|
self:_replaceAttribute(original, replacement, 'getText')
|
|
|
|
self:_replaceAttribute(original, replacement, 'displayCmd')
|
|
|
|
self:_replaceAttribute(original, replacement, 'music')
|
|
|
|
end
|
|
|
|
|
|
|
|
function C:_replaceAttribute(original, replacement, attribute)
|
|
|
|
local originalTexts = original[attribute]
|
2018-12-15 19:10:40 +00:00
|
|
|
if originalTexts == nil or originalTexts == '' then
|
2018-12-15 04:22:03 +00:00
|
|
|
return
|
|
|
|
elseif type(originalTexts) == 'string' then
|
|
|
|
originalTexts = {originalTexts}
|
|
|
|
end
|
|
|
|
|
|
|
|
local replaceText = replacement[attribute] or ''
|
|
|
|
if type(replaceText) == 'table' then
|
|
|
|
replaceText = replaceText[1]
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Loop through each possible original value until we successfully replace one.
|
2018-12-15 21:12:22 +00:00
|
|
|
for _, originalText in ipairs(originalTexts) do repeat
|
|
|
|
if originalText == "" then
|
|
|
|
break -- continue
|
|
|
|
end
|
2018-12-15 04:22:03 +00:00
|
|
|
local changed
|
|
|
|
self._text, changed = self:_stringReplace(self._text, originalText, replaceText)
|
|
|
|
if changed then
|
|
|
|
return
|
|
|
|
end
|
2018-12-15 21:12:22 +00:00
|
|
|
until true end
|
2018-12-15 04:22:03 +00:00
|
|
|
|
2018-12-15 18:53:12 +00:00
|
|
|
local template = 'Unable to replace original "%s" for [%s] %s.'
|
|
|
|
logWarning(template:format(attribute, original.map, original.name))
|
2018-12-15 04:22:03 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function C:_stringReplace(text, needle, replacement)
|
2018-12-14 21:57:56 +00:00
|
|
|
local i = text:find(needle, 1, true)
|
|
|
|
if i == nil then
|
2018-12-15 04:22:03 +00:00
|
|
|
-- logWarning(('Unable to replace "%s" with "%s"'):format(needle, replacement))
|
|
|
|
return text, false
|
2018-12-14 21:57:56 +00:00
|
|
|
end
|
|
|
|
local len = needle:len()
|
|
|
|
local j = i + len - 1
|
|
|
|
assert((i % 1 == 0) and (i > 0) and (i <= j), tostring(i))
|
|
|
|
assert((j % 1 == 0), tostring(j))
|
|
|
|
local a = text:sub(1, i - 1)
|
|
|
|
local b = text:sub(j + 1)
|
2018-12-15 04:22:03 +00:00
|
|
|
return a .. replacement .. b, true
|
2018-12-14 19:51:03 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function C:writeTo(path)
|
2018-12-15 02:49:12 +00:00
|
|
|
logInfo('writing TSC to: ' .. path)
|
2018-12-14 21:57:56 +00:00
|
|
|
|
2018-12-15 02:49:12 +00:00
|
|
|
local file, err = io.open(path, MODE_WRITE_ERASE_EXISTING)
|
2018-12-14 19:51:03 +00:00
|
|
|
assert(err == nil, err)
|
2018-12-15 02:49:12 +00:00
|
|
|
local encoded = self:_codec(self._text, 'encode')
|
2018-12-14 21:57:56 +00:00
|
|
|
file:write(encoded)
|
|
|
|
file:flush()
|
|
|
|
file:close()
|
2018-12-14 19:51:03 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
function C:_codec(text, mode)
|
|
|
|
-- Create array of chars.
|
|
|
|
local chars = {}
|
|
|
|
text:gsub(".", function(c) table.insert(chars, c) end)
|
|
|
|
|
|
|
|
-- Determine encoding char value
|
|
|
|
local encodingCharPosition = math.floor(#chars / 2) + 1
|
|
|
|
local encodingChar = chars[encodingCharPosition]:byte()
|
|
|
|
if mode == 'decode' then
|
|
|
|
encodingChar = encodingChar * -1
|
|
|
|
elseif mode == 'encode' then
|
|
|
|
-- OK!
|
|
|
|
else
|
|
|
|
error('Unknown codec mode: ' .. tostring(mode))
|
|
|
|
end
|
|
|
|
|
|
|
|
logDebug(" filesize", #chars)
|
|
|
|
logDebug(" encoding char:", encodingChar)
|
|
|
|
logDebug(" encoding char position:", encodingCharPosition)
|
|
|
|
|
|
|
|
-- Encode or decode.
|
|
|
|
for pos, char in ipairs(chars) do
|
|
|
|
if pos ~= encodingCharPosition then
|
|
|
|
local byte = (char:byte() + encodingChar) % 256
|
|
|
|
chars[pos] = string.char(byte)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
local decoded = table.concat(chars)
|
|
|
|
|
|
|
|
-- local t = {}
|
|
|
|
-- decoded:gsub(".",function(c) table.insert(t,c) end)
|
|
|
|
-- local near = 7
|
|
|
|
-- for i = encodingCharPosition - near, encodingCharPosition + near do
|
|
|
|
-- local c = t[i]
|
|
|
|
-- logDebug(i, c, c:byte())
|
|
|
|
-- end
|
|
|
|
|
|
|
|
return decoded
|
|
|
|
end
|
|
|
|
|
|
|
|
return C
|