SP Mods
This commit is contained in:
parent
6d6e79cc15
commit
6dcbfefa9f
@ -2,15 +2,13 @@ game:addlocalizedstring("MENU_MODS", "MODS")
|
|||||||
game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.")
|
game:addlocalizedstring("MENU_MODS_DESC", "Load installed mods.")
|
||||||
game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.")
|
game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.")
|
||||||
game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3")
|
game:addlocalizedstring("LUA_MENU_MOD_DESC", "&&1\nAuthor: &&2\nVersion: &&3")
|
||||||
game:addlocalizedstring("LUA_MENU_OPEN_STORE", "Open store")
|
|
||||||
game:addlocalizedstring("LUA_MENU_OPEN_STORE_DESC", "Download and install mods.")
|
|
||||||
game:addlocalizedstring("LUA_MENU_LOADED_MOD", "Loaded mod: ^2&&1")
|
game:addlocalizedstring("LUA_MENU_LOADED_MOD", "Loaded mod: ^2&&1")
|
||||||
game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods")
|
game:addlocalizedstring("LUA_MENU_AVAILABLE_MODS", "Available mods")
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload")
|
game:addlocalizedstring("LUA_MENU_UNLOAD", "Unload")
|
||||||
game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.")
|
game:addlocalizedstring("LUA_MENU_UNLOAD_DESC", "Unload the currently loaded mod.")
|
||||||
|
|
||||||
function createdivider(menu, text)
|
function createdivider(menu, text)
|
||||||
local element = LUI.UIElement.new( {
|
local element = LUI.UIElement.new({
|
||||||
leftAnchor = true,
|
leftAnchor = true,
|
||||||
rightAnchor = true,
|
rightAnchor = true,
|
||||||
left = 0,
|
left = 0,
|
||||||
@ -26,7 +24,10 @@ function createdivider(menu, text)
|
|||||||
title_bar_text = Engine.ToUpperCase(text)
|
title_bar_text = Engine.ToUpperCase(text)
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
element.text = element:getFirstChild():getFirstChild():getNextSibling()
|
||||||
|
|
||||||
menu.list:addElement(element)
|
menu.list:addElement(element)
|
||||||
|
return element
|
||||||
end
|
end
|
||||||
|
|
||||||
function string:truncate(length)
|
function string:truncate(length)
|
||||||
@ -48,12 +49,17 @@ LUI.addmenubutton("main_campaign", {
|
|||||||
|
|
||||||
function getmodname(path)
|
function getmodname(path)
|
||||||
local name = path
|
local name = path
|
||||||
local desc = Engine.Localize("@LUA_MENU_MOD_DESC_DEFAULT", name)
|
game:addlocalizedstring(name, name)
|
||||||
|
game:addlocalizedstring("LUA_MENU_MOD_DESC_DEFAULT", "Load &&1.")
|
||||||
|
local desc = Engine.Localize("LUA_MENU_MOD_DESC_DEFAULT", name)
|
||||||
local infofile = path .. "/info.json"
|
local infofile = path .. "/info.json"
|
||||||
|
|
||||||
if (io.fileexists(infofile)) then
|
if (io.fileexists(infofile)) then
|
||||||
pcall(function()
|
pcall(function()
|
||||||
local data = json.decode(io.readfile(infofile))
|
local data = json.decode(io.readfile(infofile))
|
||||||
|
game:addlocalizedstring(data.description, data.description)
|
||||||
|
game:addlocalizedstring(data.author, data.author)
|
||||||
|
game:addlocalizedstring(data.version, data.version)
|
||||||
desc = Engine.Localize("@LUA_MENU_MOD_DESC",
|
desc = Engine.Localize("@LUA_MENU_MOD_DESC",
|
||||||
data.description, data.author, data.version)
|
data.description, data.author, data.version)
|
||||||
name = data.name
|
name = data.name
|
||||||
@ -63,7 +69,7 @@ function getmodname(path)
|
|||||||
return name, desc
|
return name, desc
|
||||||
end
|
end
|
||||||
|
|
||||||
LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1)
|
LUI.MenuBuilder.registerType("mods_menu", function(a1)
|
||||||
local menu = LUI.MenuTemplate.new(a1, {
|
local menu = LUI.MenuTemplate.new(a1, {
|
||||||
menu_title = "@MENU_MODS",
|
menu_title = "@MENU_MODS",
|
||||||
exclusiveController = 0,
|
exclusiveController = 0,
|
||||||
@ -74,7 +80,8 @@ LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1)
|
|||||||
|
|
||||||
local modfolder = game:getloadedmod()
|
local modfolder = game:getloadedmod()
|
||||||
if (modfolder ~= "") then
|
if (modfolder ~= "") then
|
||||||
createdivider(menu, Engine.Localize("@LUA_MENU_LOADED_MOD", getmodname(modfolder):truncate(24)))
|
local name = getmodname(modfolder)
|
||||||
|
createdivider(menu, Engine.Localize("@LUA_MENU_LOADED_MOD", name:truncate(24)))
|
||||||
|
|
||||||
menu:AddButton("@LUA_MENU_UNLOAD", function()
|
menu:AddButton("@LUA_MENU_UNLOAD", function()
|
||||||
Engine.Exec("unloadmod")
|
Engine.Exec("unloadmod")
|
||||||
@ -113,4 +120,4 @@ LUI.MenuBuilder.m_types_build["mods_menu"] = function(a1)
|
|||||||
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
|
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
|
||||||
|
|
||||||
return menu
|
return menu
|
||||||
end
|
end)
|
||||||
|
@ -114,7 +114,7 @@ namespace fonts
|
|||||||
}
|
}
|
||||||
|
|
||||||
data_.fonts.clear();
|
data_.fonts.clear();
|
||||||
utils::hook::set<int>(SELECT_VALUE(0x14F09DBB8, 0x14FD61EE8), 0); // reset registered font count
|
utils::hook::set<int>(SELECT_VALUE(0xF793E38_b, 0xE962188_b), 0); // reset registered font count
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,9 +128,9 @@ namespace fonts
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::hook::call(SELECT_VALUE(0x1404D41B6, 0x1405D9296), db_find_xasset_header_stub);
|
utils::hook::call(SELECT_VALUE(0x55C596_b, 0x67F6E6_b), db_find_xasset_header_stub);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//REGISTER_COMPONENT(fonts::component)
|
REGISTER_COMPONENT(fonts::component)
|
||||||
|
@ -117,15 +117,10 @@ namespace logger
|
|||||||
public:
|
public:
|
||||||
void post_unpack() override
|
void post_unpack() override
|
||||||
{
|
{
|
||||||
if (game::environment::is_mp())
|
// lua stuff
|
||||||
{
|
utils::hook::jump(SELECT_VALUE(0x106010_b, 0x27CBB0_b), print_dev); // debug
|
||||||
// utils::hook::jump(0x27E210_b, print_warning, true);
|
utils::hook::jump(SELECT_VALUE(0x107680_b, 0x27E210_b), print_error); // error
|
||||||
utils::hook::jump(0x27CBB0_b, print_dev, true);
|
utils::hook::jump(SELECT_VALUE(0x0E6E30_b, 0x1F6140_b), printf); // print
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// lua print
|
|
||||||
utils::hook::jump(SELECT_VALUE(0x0E6E30_b, 0x1F6140_b), printf);
|
|
||||||
|
|
||||||
com_error_hook.create(game::Com_Error, com_error_stub);
|
com_error_hook.create(game::Com_Error, com_error_stub);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,7 @@ namespace mods
|
|||||||
utils::io::create_directory("mods");
|
utils::io::create_directory("mods");
|
||||||
}
|
}
|
||||||
|
|
||||||
db_release_xassets_hook.create(SELECT_VALUE(0x0, 0x399740_b), db_release_xassets_stub);
|
db_release_xassets_hook.create(SELECT_VALUE(0x1F4DB0_b, 0x399740_b), db_release_xassets_stub);
|
||||||
|
|
||||||
command::add("loadmod", [](const command::params& params)
|
command::add("loadmod", [](const command::params& params)
|
||||||
{
|
{
|
||||||
@ -116,4 +116,4 @@ namespace mods
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
//REGISTER_COMPONENT(mods::component)
|
REGISTER_COMPONENT(mods::component)
|
||||||
|
@ -13,6 +13,9 @@
|
|||||||
#include "fps.hpp"
|
#include "fps.hpp"
|
||||||
#include "server_list.hpp"
|
#include "server_list.hpp"
|
||||||
#include "filesystem.hpp"
|
#include "filesystem.hpp"
|
||||||
|
#include "mods.hpp"
|
||||||
|
#include "fastfiles.hpp"
|
||||||
|
#include "scripting.hpp"
|
||||||
|
|
||||||
#include "game/ui_scripting/execution.hpp"
|
#include "game/ui_scripting/execution.hpp"
|
||||||
#include "game/scripting/execution.hpp"
|
#include "game/scripting/execution.hpp"
|
||||||
@ -37,6 +40,7 @@ namespace ui_scripting
|
|||||||
utils::hook::detour hks_load_hook;
|
utils::hook::detour hks_load_hook;
|
||||||
|
|
||||||
const auto lui_common = utils::nt::load_resource(LUI_COMMON);
|
const auto lui_common = utils::nt::load_resource(LUI_COMMON);
|
||||||
|
const auto lua_json = utils::nt::load_resource(LUA_JSON);
|
||||||
|
|
||||||
struct script
|
struct script
|
||||||
{
|
{
|
||||||
@ -211,6 +215,122 @@ namespace ui_scripting
|
|||||||
{
|
{
|
||||||
localized_strings::override(string, value);
|
localized_strings::override(string, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
game_type["sharedset"] = [](const game&, const std::string& key, const std::string& value)
|
||||||
|
{
|
||||||
|
scripting::shared_table.access([key, value](scripting::shared_table_t& table)
|
||||||
|
{
|
||||||
|
table[key] = value;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
game_type["sharedget"] = [](const game&, const std::string& key)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
scripting::shared_table.access([key, &result](scripting::shared_table_t& table)
|
||||||
|
{
|
||||||
|
result = table[key];
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
game_type["sharedclear"] = [](const game&)
|
||||||
|
{
|
||||||
|
scripting::shared_table.access([](scripting::shared_table_t& table)
|
||||||
|
{
|
||||||
|
table.clear();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
game_type["assetlist"] = [](const game&, const std::string& type_string)
|
||||||
|
{
|
||||||
|
auto table_ = table();
|
||||||
|
auto index = 1;
|
||||||
|
auto type_index = -1;
|
||||||
|
|
||||||
|
for (auto i = 0; i < ::game::XAssetType::ASSET_TYPE_COUNT; i++)
|
||||||
|
{
|
||||||
|
if (type_string == ::game::g_assetNames[i])
|
||||||
|
{
|
||||||
|
type_index = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type_index == -1)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Asset type does not exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto type = static_cast<::game::XAssetType>(type_index);
|
||||||
|
fastfiles::enum_assets(type, [type, &table_, &index](const ::game::XAssetHeader header)
|
||||||
|
{
|
||||||
|
const auto asset = ::game::XAsset{type, header};
|
||||||
|
const std::string asset_name = ::game::DB_GetXAssetName(&asset);
|
||||||
|
table_[index++] = asset_name;
|
||||||
|
}, true);
|
||||||
|
|
||||||
|
return table_;
|
||||||
|
};
|
||||||
|
|
||||||
|
game_type["getweapondisplayname"] = [](const game&, const std::string& name)
|
||||||
|
{
|
||||||
|
const auto alternate = name.starts_with("alt_");
|
||||||
|
const auto weapon = ::game::G_GetWeaponForName(name.data());
|
||||||
|
|
||||||
|
char buffer[0x400] = {0};
|
||||||
|
::game::CG_GetWeaponDisplayName(weapon, alternate, buffer, 0x400);
|
||||||
|
|
||||||
|
return std::string(buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
game_type["getloadedmod"] = [](const game&)
|
||||||
|
{
|
||||||
|
return mods::mod_path;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (::game::environment::is_sp())
|
||||||
|
{
|
||||||
|
using player = table;
|
||||||
|
auto player_type = player();
|
||||||
|
lua["player"] = player_type;
|
||||||
|
|
||||||
|
player_type["notify"] = [](const player&, const std::string& name, const variadic_args& va)
|
||||||
|
{
|
||||||
|
if (!::game::CL_IsCgameInitialized() || !::game::SV_Loaded())
|
||||||
|
{
|
||||||
|
throw std::runtime_error("Not in game");
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto to_string = get_globals()["tostring"];
|
||||||
|
const auto arguments = get_return_values();
|
||||||
|
std::vector<std::string> args{};
|
||||||
|
for (const auto& value : va)
|
||||||
|
{
|
||||||
|
const auto value_str = to_string(value);
|
||||||
|
|
||||||
|
args.push_back(value_str[0].as<std::string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
::scheduler::once([name, args]()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::vector<scripting::script_value> arguments{};
|
||||||
|
|
||||||
|
for (const auto& arg : args)
|
||||||
|
{
|
||||||
|
arguments.push_back(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto player = scripting::call("getentbynum", {0}).as<scripting::entity>();
|
||||||
|
scripting::notify(player, name, arguments);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}, ::scheduler::pipeline::server);
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void start()
|
void start()
|
||||||
@ -227,6 +347,7 @@ namespace ui_scripting
|
|||||||
lua["luiglobals"] = lua;
|
lua["luiglobals"] = lua;
|
||||||
|
|
||||||
load_script("lui_common", lui_common);
|
load_script("lui_common", lui_common);
|
||||||
|
load_script("lua_json", lua_json);
|
||||||
|
|
||||||
for (const auto& path : filesystem::get_search_paths())
|
for (const auto& path : filesystem::get_search_paths())
|
||||||
{
|
{
|
||||||
@ -379,46 +500,8 @@ namespace ui_scripting
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
utils::hook::jump(SELECT_VALUE(0xE7419_b, 0x25E809_b), utils::hook::assemble([](utils::hook::assembler& a)
|
utils::hook::call(SELECT_VALUE(0xE7419_b, 0x25E809_b), db_find_xasset_header_stub);
|
||||||
{
|
utils::hook::call(SELECT_VALUE(0xE72CB_b, 0x25E6BB_b), db_find_xasset_header_stub);
|
||||||
const auto loc = a.newLabel();
|
|
||||||
|
|
||||||
a.push(rax);
|
|
||||||
a.pushad64();
|
|
||||||
a.call_aligned(db_find_xasset_header_stub);
|
|
||||||
a.mov(qword_ptr(rsp, 0x80), rax);
|
|
||||||
a.popad64();
|
|
||||||
a.pop(rax);
|
|
||||||
|
|
||||||
a.mov(rcx, r13);
|
|
||||||
a.test(rax, rax);
|
|
||||||
a.jnz(loc);
|
|
||||||
|
|
||||||
a.jmp(SELECT_VALUE(0xE7426_b, 0x25E816_b));
|
|
||||||
|
|
||||||
a.bind(loc);
|
|
||||||
a.jmp(SELECT_VALUE(0xE748A_b, 0x25E87A_b));
|
|
||||||
}), true);
|
|
||||||
|
|
||||||
utils::hook::jump(SELECT_VALUE(0xE72CB_b, 0x25E6BB_b), utils::hook::assemble([](utils::hook::assembler& a)
|
|
||||||
{
|
|
||||||
const auto loc = a.newLabel();
|
|
||||||
|
|
||||||
a.push(rax);
|
|
||||||
a.pushad64();
|
|
||||||
a.call_aligned(db_find_xasset_header_stub);
|
|
||||||
a.mov(qword_ptr(rsp, 0x80), rax);
|
|
||||||
a.popad64();
|
|
||||||
a.pop(rax);
|
|
||||||
|
|
||||||
a.test(rax, rax);
|
|
||||||
a.jnz(loc);
|
|
||||||
|
|
||||||
a.jmp(SELECT_VALUE(0xE72D9_b, 0x25E6C9_b));
|
|
||||||
|
|
||||||
a.bind(loc);
|
|
||||||
a.jmp(SELECT_VALUE(0xE73EC_b, 0x25E7DC_b));
|
|
||||||
}), true);
|
|
||||||
|
|
||||||
hks_load_hook.create(SELECT_VALUE(0xB46F0_b, 0x22C180_b), hks_load_stub);
|
hks_load_hook.create(SELECT_VALUE(0xB46F0_b, 0x22C180_b), hks_load_stub);
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ namespace game
|
|||||||
WEAK symbol<void(float, float, int)> Com_SetSlowMotion{0x0, 0x17E5F0};
|
WEAK symbol<void(float, float, int)> Com_SetSlowMotion{0x0, 0x17E5F0};
|
||||||
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x384820, 0x159860};
|
WEAK symbol<void(errorParm code, const char* message, ...)> Com_Error{0x384820, 0x159860};
|
||||||
WEAK symbol<void()> Com_Quit_f{0x0, 0x1F9280};
|
WEAK symbol<void()> Com_Quit_f{0x0, 0x1F9280};
|
||||||
WEAK symbol<void(char const* finalMessage)> Com_Shutdown{0x0, 0x0};
|
WEAK symbol<void(char const* finalMessage)> Com_Shutdown{0x3A6A50, 0x0};
|
||||||
|
|
||||||
WEAK symbol<void()> Quit{0x3A5A20, 0x17CF50};
|
WEAK symbol<void()> Quit{0x3A5A20, 0x17CF50};
|
||||||
|
|
||||||
@ -64,7 +64,7 @@ namespace game
|
|||||||
WEAK symbol<void(int localClientNum, /*mp::cg_s**/void* cg,
|
WEAK symbol<void(int localClientNum, /*mp::cg_s**/void* cg,
|
||||||
const char* dvar, const char* value)> CG_SetClientDvarFromServer{0x0, 0x0};
|
const char* dvar, const char* value)> CG_SetClientDvarFromServer{0x0, 0x0};
|
||||||
WEAK symbol<char*(const unsigned int weapon,
|
WEAK symbol<char*(const unsigned int weapon,
|
||||||
bool isAlternate, char* outputBuffer, int bufferLen)> CG_GetWeaponDisplayName{0x0, 0x0};
|
bool isAlternate, char* outputBuffer, int bufferLen)> CG_GetWeaponDisplayName{0x192B80, 0x104B70};
|
||||||
|
|
||||||
WEAK symbol<bool()> CL_IsCgameInitialized{0x1A3210, 0x33C640};
|
WEAK symbol<bool()> CL_IsCgameInitialized{0x1A3210, 0x33C640};
|
||||||
WEAK symbol<void(int a1)> CL_VirtualLobbyShutdown{0x0, 0x0};
|
WEAK symbol<void(int a1)> CL_VirtualLobbyShutdown{0x0, 0x0};
|
||||||
@ -109,7 +109,7 @@ namespace game
|
|||||||
|
|
||||||
WEAK symbol<void()> G_Glass_Update{0x2992E0, 0x417940};
|
WEAK symbol<void()> G_Glass_Update{0x2992E0, 0x417940};
|
||||||
WEAK symbol<int(int clientNum)> G_GetClientScore{0x0, 0x0};
|
WEAK symbol<int(int clientNum)> G_GetClientScore{0x0, 0x0};
|
||||||
WEAK symbol<unsigned int(const char* name)> G_GetWeaponForName{0x4B18D0, 0x461180};
|
WEAK symbol<unsigned int(const char* name)> G_GetWeaponForName{0x2F20F0, 0x461180};
|
||||||
WEAK symbol<int(playerState_s* ps, unsigned int weapon, int dualWield,
|
WEAK symbol<int(playerState_s* ps, unsigned int weapon, int dualWield,
|
||||||
int startInAltMode, int, int, int, char, ...)> G_GivePlayerWeapon{0x2F24F0, 0x461600};
|
int startInAltMode, int, int, int, char, ...)> G_GivePlayerWeapon{0x2F24F0, 0x461600};
|
||||||
WEAK symbol<void(playerState_s* ps, unsigned int weapon, int hadWeapon)> G_InitializeAmmo{0x0, 0x41C170};
|
WEAK symbol<void(playerState_s* ps, unsigned int weapon, int hadWeapon)> G_InitializeAmmo{0x0, 0x41C170};
|
||||||
@ -198,7 +198,7 @@ namespace game
|
|||||||
WEAK symbol<int(int clientNum)> SV_GetClientPing{0x0, 0x551D70};
|
WEAK symbol<int(int clientNum)> SV_GetClientPing{0x0, 0x551D70};
|
||||||
WEAK symbol<playerState_s* (int num)> SV_GetPlayerstateForClientNum{0x0, 0x551E10};
|
WEAK symbol<playerState_s* (int num)> SV_GetPlayerstateForClientNum{0x0, 0x551E10};
|
||||||
WEAK symbol<void(int index, const char* string)> SV_SetConfigstring{0x0, 0x553E60};
|
WEAK symbol<void(int index, const char* string)> SV_SetConfigstring{0x0, 0x553E60};
|
||||||
WEAK symbol<bool()> SV_Loaded{0x0, 0x553970};
|
WEAK symbol<bool()> SV_Loaded{0x4C4810, 0x553970};
|
||||||
WEAK symbol<void(int clientNum, const char* reason)> SV_KickClientNum{0x0, 0x54C060};
|
WEAK symbol<void(int clientNum, const char* reason)> SV_KickClientNum{0x0, 0x54C060};
|
||||||
WEAK symbol<bool(const char* map)> SV_MapExists{0x0, 0x54C0C0};
|
WEAK symbol<bool(const char* map)> SV_MapExists{0x0, 0x54C0C0};
|
||||||
WEAK symbol<void(mp::client_t*, const char*, int)> SV_ExecuteClientCommand{0x0, 0x0};
|
WEAK symbol<void(mp::client_t*, const char*, int)> SV_ExecuteClientCommand{0x0, 0x0};
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
-- SOFTWARE.
|
-- SOFTWARE.
|
||||||
--
|
--
|
||||||
|
|
||||||
local json = { _version = "0.1.2" }
|
json = { _version = "0.1.2" }
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
-------------------------------------------------------------------------------
|
||||||
-- Encode
|
-- Encode
|
||||||
|
Loading…
Reference in New Issue
Block a user