Threater stuff.
This commit is contained in:
parent
0bcbd75787
commit
d28cbd956e
@ -85,8 +85,6 @@ namespace Components
|
||||
|
||||
std::string rotation = Dvar::Var("sv_mapRotationCurrent").Get<std::string>();
|
||||
|
||||
// Ignores " for now, too lazy to implement
|
||||
// TODO: Implement!
|
||||
auto tokens = Utils::Explode(rotation, ' ');
|
||||
|
||||
for (unsigned int i = 0; i < (tokens.size() - 1); i += 2)
|
||||
@ -122,11 +120,6 @@ namespace Components
|
||||
Logger::Print("Applying new gametype: %s\n", value.data());
|
||||
Dvar::Var("g_gametype").Set(value);
|
||||
}
|
||||
else if (key == "fs_game")
|
||||
{
|
||||
Logger::Print("Applying new mod: %s\n", value.data());
|
||||
Dvar::Var("fs_game").Set(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger::Print("Unsupported maprotation key '%s', motherfucker!\n", key.data());
|
||||
|
@ -23,6 +23,57 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystem::FileWriter::Write(std::string data)
|
||||
{
|
||||
if (this->Handle)
|
||||
{
|
||||
Game::FS_Write(data.data(), data.size(), this->Handle);
|
||||
}
|
||||
}
|
||||
|
||||
void FileSystem::FileWriter::Open()
|
||||
{
|
||||
this->Handle = Game::FS_FOpenFileWrite(this->FilePath.data());
|
||||
}
|
||||
|
||||
void FileSystem::FileWriter::Close()
|
||||
{
|
||||
if (this->Handle)
|
||||
{
|
||||
Game::FS_FCloseFile(this->Handle);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> FileSystem::GetFileList(std::string path, std::string extension)
|
||||
{
|
||||
std::vector<std::string> fileList;
|
||||
|
||||
int numFiles = 0;
|
||||
char** files = Game::FS_ListFiles(path.data(), extension.data(), Game::FS_LIST_PURE_ONLY, &numFiles, 0);
|
||||
|
||||
if (files)
|
||||
{
|
||||
for (int i = 0; i < numFiles; i++)
|
||||
{
|
||||
if (files[i])
|
||||
{
|
||||
fileList.push_back(files[i]);
|
||||
}
|
||||
}
|
||||
|
||||
Game::FS_FreeFileList(files);
|
||||
}
|
||||
|
||||
return fileList;
|
||||
}
|
||||
|
||||
void FileSystem::DeleteFile(std::string folder, std::string file)
|
||||
{
|
||||
char path[MAX_PATH] = { 0 };
|
||||
Game::FS_BuildPathToFile(Dvar::Var("fs_basepath").Get<const char*>(), (char*)0x63D0BB8, Utils::VA("%s/%s", folder.data(), file.data()), (char**)&path);
|
||||
Game::FS_Remove(path);
|
||||
}
|
||||
|
||||
int FileSystem::ExecIsFSStub(const char* execFilename)
|
||||
{
|
||||
return !File(execFilename).Exists();
|
||||
|
@ -21,9 +21,28 @@ namespace Components
|
||||
void Read();
|
||||
};
|
||||
|
||||
class FileWriter
|
||||
{
|
||||
public:
|
||||
FileWriter(std::string file) : FilePath(file), Handle(0) { this->Open(); };
|
||||
~FileWriter() { this->Close(); };
|
||||
|
||||
void Write(std::string data);
|
||||
|
||||
private:
|
||||
int Handle;
|
||||
std::string FilePath;
|
||||
|
||||
void Open();
|
||||
void Close();
|
||||
};
|
||||
|
||||
FileSystem();
|
||||
const char* GetName() { return "FileSystem"; };
|
||||
|
||||
static std::vector<std::string> GetFileList(std::string path, std::string extension);
|
||||
static void DeleteFile(std::string folder, std::string file);
|
||||
|
||||
private:
|
||||
static int ExecIsFSStub(const char* execFilename);
|
||||
};
|
||||
|
@ -4,14 +4,14 @@ namespace Components
|
||||
{
|
||||
ServerInfo::Container ServerInfo::PlayerContainer;
|
||||
|
||||
int ServerInfo::GetPlayerCount()
|
||||
unsigned int ServerInfo::GetPlayerCount()
|
||||
{
|
||||
return ServerInfo::PlayerContainer.PlayerList.size();
|
||||
}
|
||||
|
||||
const char* ServerInfo::GetPlayerText(int index, int column)
|
||||
const char* ServerInfo::GetPlayerText(unsigned int index, int column)
|
||||
{
|
||||
if ((unsigned int)index < ServerInfo::PlayerContainer.PlayerList.size())
|
||||
if (index < ServerInfo::PlayerContainer.PlayerList.size())
|
||||
{
|
||||
switch (column)
|
||||
{
|
||||
@ -32,7 +32,7 @@ namespace Components
|
||||
return "";
|
||||
}
|
||||
|
||||
void ServerInfo::SelectPlayer(int index)
|
||||
void ServerInfo::SelectPlayer(unsigned int index)
|
||||
{
|
||||
ServerInfo::PlayerContainer.CurrentPlayer = index;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace Components
|
||||
std::string Name;
|
||||
};
|
||||
|
||||
int CurrentPlayer;
|
||||
unsigned int CurrentPlayer;
|
||||
std::vector<Player> PlayerList;
|
||||
Network::Address Target;
|
||||
};
|
||||
@ -26,8 +26,8 @@ namespace Components
|
||||
|
||||
static void ServerStatus();
|
||||
|
||||
static int GetPlayerCount();
|
||||
static const char* GetPlayerText(int index, int column);
|
||||
static void SelectPlayer(int index);
|
||||
static unsigned int GetPlayerCount();
|
||||
static const char* GetPlayerText(unsigned int index, int column);
|
||||
static void SelectPlayer(unsigned int index);
|
||||
};
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace Components
|
||||
std::vector<ServerList::ServerInfo> ServerList::OfflineList;
|
||||
std::vector<ServerList::ServerInfo> ServerList::FavouriteList;
|
||||
|
||||
std::vector<int> ServerList::VisibleList;
|
||||
std::vector<unsigned int> ServerList::VisibleList;
|
||||
|
||||
std::vector<ServerList::ServerInfo>* ServerList::GetList()
|
||||
{
|
||||
@ -49,12 +49,12 @@ namespace Components
|
||||
return (Dvar::Var("ui_netSource").Get<int>() == 1);
|
||||
}
|
||||
|
||||
int ServerList::GetServerCount()
|
||||
unsigned int ServerList::GetServerCount()
|
||||
{
|
||||
return (int)ServerList::VisibleList.size();
|
||||
return ServerList::VisibleList.size();
|
||||
}
|
||||
|
||||
const char* ServerList::GetServerText(int index, int column)
|
||||
const char* ServerList::GetServerText(unsigned int index, int column)
|
||||
{
|
||||
ServerList::ServerInfo* info = ServerList::GetServer(index);
|
||||
|
||||
@ -121,9 +121,9 @@ namespace Components
|
||||
return "";
|
||||
}
|
||||
|
||||
void ServerList::SelectServer(int index)
|
||||
void ServerList::SelectServer(unsigned int index)
|
||||
{
|
||||
ServerList::CurrentServer = (unsigned int)index;
|
||||
ServerList::CurrentServer = index;
|
||||
|
||||
ServerList::ServerInfo* info = ServerList::GetCurrentServer();
|
||||
|
||||
@ -412,8 +412,8 @@ namespace Components
|
||||
{
|
||||
qsort(ServerList::VisibleList.data(), ServerList::VisibleList.size(), sizeof(int), [] (const void* first, const void* second)
|
||||
{
|
||||
int server1 = *(int*)first;
|
||||
int server2 = *(int*)second;
|
||||
unsigned int server1 = *(unsigned int*)first;
|
||||
unsigned int server2 = *(unsigned int*)second;
|
||||
|
||||
ServerInfo* info1 = nullptr;
|
||||
ServerInfo* info2 = nullptr;
|
||||
@ -421,8 +421,8 @@ namespace Components
|
||||
auto list = ServerList::GetList();
|
||||
if (!list) return 0;
|
||||
|
||||
if (list->size() > (unsigned int)server1) info1 = &(*list)[server1];
|
||||
if (list->size() > (unsigned int)server2) info2 = &(*list)[server2];
|
||||
if (list->size() > server1) info1 = &(*list)[server1];
|
||||
if (list->size() > server2) info2 = &(*list)[server2];
|
||||
|
||||
if (!info1) return 1;
|
||||
if (!info2) return -1;
|
||||
@ -445,14 +445,14 @@ namespace Components
|
||||
});
|
||||
}
|
||||
|
||||
ServerList::ServerInfo* ServerList::GetServer(int index)
|
||||
ServerList::ServerInfo* ServerList::GetServer(unsigned int index)
|
||||
{
|
||||
if (ServerList::VisibleList.size() > (unsigned int)index)
|
||||
if (ServerList::VisibleList.size() > index)
|
||||
{
|
||||
auto list = ServerList::GetList();
|
||||
if (!list) return nullptr;
|
||||
|
||||
if (list->size() > (unsigned int)ServerList::VisibleList[index])
|
||||
if (list->size() > ServerList::VisibleList[index])
|
||||
{
|
||||
return &(*list)[ServerList::VisibleList[index]];
|
||||
}
|
||||
|
@ -93,10 +93,10 @@ namespace Components
|
||||
std::mutex Mutex;
|
||||
};
|
||||
|
||||
static int GetServerCount();
|
||||
static const char* GetServerText(int index, int column);
|
||||
static unsigned int GetServerCount();
|
||||
static const char* GetServerText(unsigned int index, int column);
|
||||
static const char* GetServerText(ServerInfo* server, int column);
|
||||
static void SelectServer(int index);
|
||||
static void SelectServer(unsigned int index);
|
||||
|
||||
static void UpdateSource();
|
||||
static void UpdateGameType();
|
||||
@ -108,7 +108,7 @@ namespace Components
|
||||
static void LoadFavourties();
|
||||
static void StoreFavourite(std::string server);
|
||||
|
||||
static ServerInfo* GetServer(int index);
|
||||
static ServerInfo* GetServer(unsigned int index);
|
||||
static std::vector<ServerInfo>* GetList();
|
||||
|
||||
static int SortKey;
|
||||
@ -121,6 +121,6 @@ namespace Components
|
||||
static std::vector<ServerInfo> OfflineList;
|
||||
static std::vector<ServerInfo> FavouriteList;
|
||||
|
||||
static std::vector<int> VisibleList;
|
||||
static std::vector<unsigned int> VisibleList;
|
||||
};
|
||||
}
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace Components
|
||||
{
|
||||
Theatre::Container Theatre::DemoContainer;
|
||||
|
||||
char Theatre::BaselineSnapshot[131072] = { 0 };
|
||||
PBYTE Theatre::BaselineSnapshotMsg = 0;
|
||||
int Theatre::BaselineSnapshotMsgLen;
|
||||
@ -128,18 +130,18 @@ namespace Components
|
||||
}
|
||||
|
||||
void __declspec(naked) Theatre::UISetActiveMenuStub()
|
||||
{
|
||||
if (*Game::demoPlaying == 1)
|
||||
{
|
||||
__asm
|
||||
{
|
||||
mov eax, Game::demoPlaying
|
||||
mov eax, [eax]
|
||||
test al, al
|
||||
jz continue
|
||||
|
||||
mov eax, 4CB49Ch
|
||||
jmp eax
|
||||
}
|
||||
}
|
||||
|
||||
__asm
|
||||
{
|
||||
continue:
|
||||
mov ecx, [esp + 10h]
|
||||
push 10h
|
||||
push ecx
|
||||
@ -148,6 +150,125 @@ namespace Components
|
||||
}
|
||||
}
|
||||
|
||||
void Theatre::RecordStub(int channel, char* message, char* file)
|
||||
{
|
||||
Game::Com_Printf(channel, message, file);
|
||||
|
||||
Theatre::DemoContainer.CurrentInfo.Name = file;
|
||||
Theatre::DemoContainer.CurrentInfo.Mapname = Dvar::Var("mapname").Get<const char*>();
|
||||
Theatre::DemoContainer.CurrentInfo.Gametype = Dvar::Var("g_gametype").Get<const char*>();
|
||||
Theatre::DemoContainer.CurrentInfo.Author = Steam::SteamFriends()->GetPersonaName();
|
||||
Theatre::DemoContainer.CurrentInfo.Length = Game::Com_Milliseconds();
|
||||
std::time(&Theatre::DemoContainer.CurrentInfo.TimeStamp);
|
||||
}
|
||||
|
||||
void Theatre::StopRecordStub(int channel, char* message)
|
||||
{
|
||||
Game::Com_Printf(channel, message);
|
||||
|
||||
// Store correct length
|
||||
Theatre::DemoContainer.CurrentInfo.Length = Game::Com_Milliseconds() - Theatre::DemoContainer.CurrentInfo.Length;
|
||||
|
||||
// Write metadata
|
||||
FileSystem::FileWriter meta(Utils::VA("%s.json", Theatre::DemoContainer.CurrentInfo.Name.data()));
|
||||
meta.Write(json11::Json(Theatre::DemoContainer.CurrentInfo).dump());
|
||||
}
|
||||
|
||||
void Theatre::LoadDemos()
|
||||
{
|
||||
Theatre::DemoContainer.CurrentSelection = 0;
|
||||
Theatre::DemoContainer.Demos.clear();
|
||||
|
||||
auto demos = FileSystem::GetFileList("demos/", "dm_13");
|
||||
|
||||
for (auto demo : demos)
|
||||
{
|
||||
FileSystem::File meta(Utils::VA("demos/%s.json", demo.data()));
|
||||
|
||||
if (meta.Exists())
|
||||
{
|
||||
std::string error;
|
||||
json11::Json metaObject = json11::Json::parse(meta.GetBuffer(), error);
|
||||
|
||||
if (metaObject.is_object())
|
||||
{
|
||||
Theatre::Container::DemoInfo info;
|
||||
|
||||
info.Name = demo.substr(0, demo.find_last_of("."));
|
||||
info.Author = metaObject["author"].string_value();
|
||||
info.Gametype = metaObject["gametype"].string_value();
|
||||
info.Mapname = metaObject["mapname"].string_value();
|
||||
info.Length = (int)metaObject["length"].number_value();
|
||||
info.TimeStamp = _atoi64(metaObject["timestamp"].string_value().data());
|
||||
|
||||
Theatre::DemoContainer.Demos.push_back(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse, latest demo first!
|
||||
std::reverse(Theatre::DemoContainer.Demos.begin(), Theatre::DemoContainer.Demos.end());
|
||||
}
|
||||
|
||||
void Theatre::DeleteDemo()
|
||||
{
|
||||
if (Theatre::DemoContainer.CurrentSelection < Theatre::DemoContainer.Demos.size())
|
||||
{
|
||||
Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[Theatre::DemoContainer.CurrentSelection];
|
||||
|
||||
Logger::Print("Deleting demo %s...\n", info.Name.data());
|
||||
|
||||
FileSystem::DeleteFile("demos", info.Name + ".dm_13");
|
||||
FileSystem::DeleteFile("demos", info.Name + ".dm_13.json");
|
||||
|
||||
// Reload demos
|
||||
Theatre::LoadDemos();
|
||||
}
|
||||
}
|
||||
|
||||
void Theatre::PlayDemo()
|
||||
{
|
||||
if (Theatre::DemoContainer.CurrentSelection < Theatre::DemoContainer.Demos.size())
|
||||
{
|
||||
Command::Execute(Utils::VA("demo %s", Theatre::DemoContainer.Demos[Theatre::DemoContainer.CurrentSelection].Name.data()), true);
|
||||
Command::Execute("demoback", false);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Theatre::GetDemoCount()
|
||||
{
|
||||
return Theatre::DemoContainer.Demos.size();
|
||||
}
|
||||
|
||||
// Omit column here
|
||||
const char* Theatre::GetDemoText(unsigned int item, int column)
|
||||
{
|
||||
if (item < Theatre::DemoContainer.Demos.size())
|
||||
{
|
||||
Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[item];
|
||||
|
||||
return Utils::VA("%s on %s", Game::UI_LocalizeGameType(info.Gametype.data()), Game::UI_LocalizeMapName(info.Mapname.data()));
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
void Theatre::SelectDemo(unsigned int index)
|
||||
{
|
||||
if (index < Theatre::DemoContainer.Demos.size())
|
||||
{
|
||||
Theatre::DemoContainer.CurrentSelection = index;
|
||||
Theatre::Container::DemoInfo info = Theatre::DemoContainer.Demos[index];
|
||||
|
||||
Dvar::Var("ui_demo_mapname").Set(info.Mapname);
|
||||
Dvar::Var("ui_demo_mapname_localized").Set(Game::UI_LocalizeMapName(info.Mapname.data()));
|
||||
Dvar::Var("ui_demo_gametype").Set(Game::UI_LocalizeGameType(info.Gametype.data()));
|
||||
Dvar::Var("ui_demo_length").Set(info.Length); // TODO: Parse as readable string
|
||||
Dvar::Var("ui_demo_author").Set(info.Author);
|
||||
Dvar::Var("ui_demo_date").Set(std::asctime(std::localtime(&info.TimeStamp)));
|
||||
}
|
||||
}
|
||||
|
||||
Theatre::Theatre()
|
||||
{
|
||||
Utils::Hook(0x5A8370, Theatre::GamestateWriteStub, HOOK_CALL).Install()->Quick();
|
||||
@ -158,10 +279,23 @@ namespace Components
|
||||
Utils::Hook(0x50320E, Theatre::AdjustTimeDeltaStub, HOOK_CALL).Install()->Quick();
|
||||
Utils::Hook(0x5A8E03, Theatre::ServerTimedOutStub, HOOK_JUMP).Install()->Quick();
|
||||
|
||||
// Hook commands to enforce metadata generation
|
||||
Utils::Hook(0x5A82AE, Theatre::RecordStub, HOOK_CALL).Install()->Quick();
|
||||
Utils::Hook(0x5A8156, Theatre::StopRecordStub, HOOK_CALL).Install()->Quick();
|
||||
|
||||
// UIScripts
|
||||
UIScript::Add("loadDemos", Theatre::LoadDemos);
|
||||
UIScript::Add("launchDemo", Theatre::PlayDemo);
|
||||
UIScript::Add("deleteDemo", Theatre::DeleteDemo);
|
||||
|
||||
// Feeder
|
||||
UIFeeder::Add(10.0f, Theatre::GetDemoCount, Theatre::GetDemoText, Theatre::SelectDemo);
|
||||
|
||||
// set the configstrings stuff to load the default (empty) string table; this should allow demo recording on all gametypes/maps
|
||||
if(!Dedicated::IsDedicated()) Utils::Hook::Set<char*>(0x47440B, "mp/defaultStringTable.csv");
|
||||
|
||||
*(BYTE*)0x5AC854 = 4;
|
||||
*(BYTE*)0x5AC85A = 4;
|
||||
// Change font size
|
||||
Utils::Hook::Set<BYTE>(0x5AC854, 2);
|
||||
Utils::Hook::Set<BYTE>(0x5AC85A, 2);
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,37 @@ namespace Components
|
||||
const char* GetName() { return "Theatre"; };
|
||||
|
||||
private:
|
||||
struct Container
|
||||
{
|
||||
struct DemoInfo
|
||||
{
|
||||
std::string Name;
|
||||
std::string Mapname;
|
||||
std::string Gametype;
|
||||
std::string Author;
|
||||
int Length;
|
||||
std::time_t TimeStamp;
|
||||
|
||||
json11::Json to_json() const
|
||||
{
|
||||
return json11::Json::object
|
||||
{
|
||||
{ "mapname", Mapname },
|
||||
{ "gametype", Gametype },
|
||||
{ "author", Author },
|
||||
{ "length", Length },
|
||||
{ "timestamp", Utils::VA("%lld", TimeStamp) } //Ugly, but prevents information loss
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
DemoInfo CurrentInfo;
|
||||
unsigned int CurrentSelection;
|
||||
std::vector<DemoInfo> Demos;
|
||||
};
|
||||
|
||||
static Container DemoContainer;
|
||||
|
||||
static char BaselineSnapshot[131072];
|
||||
static PBYTE BaselineSnapshotMsg;
|
||||
static int BaselineSnapshotMsgLen;
|
||||
@ -14,6 +45,14 @@ namespace Components
|
||||
|
||||
static void WriteBaseline();
|
||||
|
||||
static void LoadDemos();
|
||||
static void DeleteDemo();
|
||||
static void PlayDemo();
|
||||
|
||||
static unsigned int GetDemoCount();
|
||||
static const char* GetDemoText(unsigned int item, int column);
|
||||
static void SelectDemo(unsigned int index);
|
||||
|
||||
static void GamestateWriteStub(Game::msg_t* msg, char byte);
|
||||
static void RecordGamestateStub();
|
||||
static void BaselineStoreStub();
|
||||
@ -21,5 +60,8 @@ namespace Components
|
||||
static void AdjustTimeDeltaStub();
|
||||
static void ServerTimedOutStub();
|
||||
static void UISetActiveMenuStub();
|
||||
|
||||
static void RecordStub(int channel, char* message, char* file);
|
||||
static void StopRecordStub(int channel, char* message);
|
||||
};
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ namespace Components
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
int UIFeeder::GetItemCount()
|
||||
unsigned int UIFeeder::GetItemCount()
|
||||
{
|
||||
if (UIFeeder::Feeders.find(UIFeeder::Current.Feeder) != UIFeeder::Feeders.end())
|
||||
{
|
||||
@ -65,7 +65,7 @@ namespace Components
|
||||
retn
|
||||
|
||||
continue:
|
||||
fld ds : 739FD0h
|
||||
fld ds:739FD0h
|
||||
|
||||
mov eax, 4C25D6h
|
||||
jmp eax
|
||||
|
@ -3,9 +3,9 @@ namespace Components
|
||||
class UIFeeder : public Component
|
||||
{
|
||||
public:
|
||||
typedef int(__cdecl * GetItemCount_t)();
|
||||
typedef const char* (__cdecl * GetItemText_t)(int index, int column);
|
||||
typedef void(__cdecl * Select_t)(int index);
|
||||
typedef unsigned int(__cdecl * GetItemCount_t)();
|
||||
typedef const char* (__cdecl * GetItemText_t)(unsigned int index, int column);
|
||||
typedef void(__cdecl * Select_t)(unsigned int index);
|
||||
|
||||
struct Callbacks
|
||||
{
|
||||
@ -31,7 +31,7 @@ namespace Components
|
||||
static Container Current;
|
||||
|
||||
static void GetItemCountStub();
|
||||
static int GetItemCount();
|
||||
static unsigned int GetItemCount();
|
||||
|
||||
static void GetItemTextStub();
|
||||
static const char* GetItemText();
|
||||
|
@ -102,7 +102,7 @@ namespace Game
|
||||
typedef void(__cdecl * FS_FreeFileList_t)(char** list);
|
||||
extern FS_FreeFileList_t FS_FreeFileList;
|
||||
|
||||
typedef int(__cdecl * FS_FOpenFileAppend_t)(char* file);
|
||||
typedef int(__cdecl * FS_FOpenFileAppend_t)(const char* file);
|
||||
extern FS_FOpenFileAppend_t FS_FOpenFileAppend;
|
||||
extern FS_FOpenFileAppend_t FS_FOpenFileWrite;
|
||||
|
||||
@ -118,7 +118,7 @@ namespace Game
|
||||
typedef bool(__cdecl * FS_WriteFile_t)(char* filename, char* folder, void* buffer, int size);
|
||||
extern FS_WriteFile_t FS_WriteFile;
|
||||
|
||||
typedef int(__cdecl * FS_Write_t)(void* buffer, size_t size, int file);
|
||||
typedef int(__cdecl * FS_Write_t)(const void* buffer, size_t size, int file);
|
||||
extern FS_Write_t FS_Write;
|
||||
|
||||
typedef int(__cdecl * FS_Read_t)(void* buffer, size_t size, int file);
|
||||
|
Loading…
Reference in New Issue
Block a user