Skip to content

Commit

Permalink
Add go files for downloader
Browse files Browse the repository at this point in the history
  • Loading branch information
OFFTKP committed Oct 23, 2023
1 parent 7ed11df commit abbbd18
Show file tree
Hide file tree
Showing 14 changed files with 9,538 additions and 5 deletions.
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE Release)
endif()

option(USE_GOLANG "Use Go, used by core updater" ON)

set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
Expand Down Expand Up @@ -54,6 +56,7 @@ find_package(Lua REQUIRED)

add_subdirectory(vendored/fmt)
add_subdirectory(vendored/argparse)
add_subdirectory(go)

# add_compile_options(-fsanitize=address -fsanitize=undefined)
# add_link_options(-fsanitize=address -fsanitize=undefined)
Expand All @@ -74,6 +77,7 @@ set(HYDRA_QT_FILES
qt/terminalwindow.cxx
vendored/miniaudio.c
vendored/stb_image_write.c
vendored/miniz/miniz.c
)

set(HYDRA_SERVER_FILES
Expand All @@ -100,6 +104,10 @@ target_link_libraries(hydra PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::OpenGL
Qt${QT_VERSION_MAJOR}::OpenGLWidgets ${CMAKE_DL_LIBS}
fmt::fmt ${LUA_LIBRARIES})
if(USE_GOLANG)
target_link_libraries(hydra PRIVATE hydra_go_static)
target_compile_definitions(hydra PRIVATE HYDRA_USE_GOLANG)
endif()
target_include_directories(hydra PRIVATE ${HYDRA_INCLUDE_DIRECTORIES})
set_target_properties(hydra PROPERTIES hydra_properties
MACOSX_BUNDLE_GUI_IDENTIFIER offtkp.hydra.com
Expand Down
17 changes: 17 additions & 0 deletions go/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
cmake_minimum_required(VERSION 3.5)
project(hydra_go)

set(HYDRA_GO_SOURCES downloader.go)
set(LIB libhydra_go.a)

add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${LIB}
DEPENDS ${HYDRA_GO_SOURCES}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND env GOPATH=${GOPATH} go build -buildmode=c-archive
-o "${CMAKE_CURRENT_BINARY_DIR}/${LIB}"
COMMENT "Building hydra_go library...")
add_custom_target(hydra_go ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/${LIB})
add_library(hydra_go_static STATIC IMPORTED GLOBAL)
set_target_properties(hydra_go_static PROPERTIES
IMPORTED_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${LIB}
INTERFACE_INCLUDE_DIRECTORIES ${CMAKE_CURRENT_SOURCE_DIR})
3 changes: 3 additions & 0 deletions go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## Why use Go in a C++ project?

Go has nice builtin HTTPS support and a lot of other goodies in its standard library. The other option was using OpenSSL, which my system (Archlinux) failed to find and it would mean an additional dependency that is harder to install on systems like Windows.
44 changes: 44 additions & 0 deletions go/downloader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package main

/*
#include "downloader.h"
#include <stdlib.h>
#include <string.h>
inline void* my_malloc(size_t size) { return malloc(size); }
*/
import "C"
import (
"io"
"net/http"
"unsafe"
)

//export hydra_download
func hydra_download(url *C.cchar_t) C.hydra_buffer_t {
var buffer C.hydra_buffer_t
buffer.data = nil
buffer.size = 0

goURL := C.GoString(url)
response, err := http.Get(goURL)
if err != nil {
println("Go: " + err.Error())
return buffer
}
defer response.Body.Close()

b, err := io.ReadAll(response.Body)
if err != nil {
println("Go: " + err.Error())
return buffer
}

buffer.data = C.my_malloc(C.size_t(response.ContentLength))
C.memcpy(buffer.data, unsafe.Pointer(&b[0]), C.size_t(response.ContentLength))
buffer.size = C.size_t(response.ContentLength)
return buffer
}

// For whatever reason, this is required to build with -buildmode=c-archive
func main() {}
21 changes: 21 additions & 0 deletions go/downloader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#ifdef __cplusplus
extern "C" {
#endif

#include <stdint.h>
#include <stdlib.h>

typedef const char cchar_t;

typedef struct {
void* data;
uint64_t size;
} hydra_buffer_t;

extern hydra_buffer_t hydra_download(cchar_t* url);

#ifdef __cplusplus
}
#endif
1 change: 1 addition & 0 deletions go/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module hydra_go
2 changes: 1 addition & 1 deletion include/core_loader.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace hydra
inline dynlib_handle_t dynlib_open(const char* path)
{
#if defined(HYDRA_LINUX) || defined(HYDRA_MACOS) || defined(HYDRA_ANDROID)
return dlopen(path, RTLD_NOW | RTLD_GLOBAL);
return dlopen(path, RTLD_LAZY | RTLD_GLOBAL);
#elif defined(HYDRA_WINDOWS)
std::wstring wpath = std::wstring(path, path + std::strlen(path));
printf("Trying to convert string to wstring to load library with loadlibraryw, this is "
Expand Down
48 changes: 48 additions & 0 deletions include/download.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#pragma once

#include "go/downloader.h"
#include <string>

namespace hydra
{
struct HydraBufferWrapper
{
HydraBufferWrapper() = default;

HydraBufferWrapper(const hydra_buffer_t& buffer) : buffer_(buffer) {}

~HydraBufferWrapper()
{
if (buffer_.data)
free(buffer_.data);
}

hydra_buffer_t* operator->()
{
return &buffer_;
}

private:
hydra_buffer_t buffer_;
};

struct Downloader
{
static HydraBufferWrapper Download(const std::string& url)
{
#ifndef HYDRA_USE_GOLANG
printf("Tried to download when built without golang support, this shouldn't happen");
return {};
#endif
try
{
hydra_buffer_t buffer = hydra_download(url.c_str());
return HydraBufferWrapper(buffer);
} catch (...)
{
printf("Failed while trying to download %s using Go\n", url.c_str());
return {};
}
}
};
} // namespace hydra
113 changes: 113 additions & 0 deletions include/update.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#pragma once

#include "json.hpp"
#include "settings.hxx"
#include <download.hxx>
#include <miniz/miniz.h>
#include <mutex>
#include <sstream>
#include <thread>

namespace hydra
{
struct Updater
{
static std::mutex& GetMutex()
{
static std::mutex mutex;
return mutex;
}

static bool NeedsDatabaseUpdate()
{
std::string old_date = Settings::Get("database_date");
if (old_date.empty())
return true;

std::string new_date = get_database_time();
return is_newer_date(old_date, new_date);
}

static void UpdateDatabase()
{
std::thread t([]() {
std::mutex& mutex = GetMutex();
std::lock_guard<std::mutex> lock(mutex);
HydraBufferWrapper buffer = Downloader::Download(
"https://github.com/hydra-emu/database/archive/refs/heads/master.zip");

if (!buffer->data)
{
log_fatal("Failed to download database zip\n");
}

mz_zip_archive zip_archive;
memset(&zip_archive, 0, sizeof(zip_archive));

if (!mz_zip_reader_init_mem(&zip_archive, buffer->data, buffer->size, 0))
log_fatal("Failed to read database zip\n");

if (!std::filesystem::create_directories(Settings::GetSavePath() / "database"))
{
if (!std::filesystem::exists(Settings::GetSavePath() / "database"))
log_fatal("Failed to create database directory\n");
}

for (size_t i = 0; i < mz_zip_reader_get_num_files(&zip_archive); i++)
{
mz_zip_archive_file_stat file_stat;
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat))
log_fatal("Failed to stat file in zip\n");

std::filesystem::path path = file_stat.m_filename;
if (path.extension() == ".json")
{
std::string data;
data.resize(file_stat.m_uncomp_size);
if (!mz_zip_reader_extract_to_mem(&zip_archive, i, data.data(), data.size(),
0))
log_fatal("Failed to extract file from zip\n");

std::ofstream file(Settings::GetSavePath() / "database" / path.filename());
file << data;
}
}
});
t.detach();
}

private:
static std::string get_database_time()
{
HydraBufferWrapper result = Downloader::Download(
"https://api.github.com/repos/hydra-emu/database/commits/master");

if (result->data)
log_fatal("Failed to download database info\n");

std::string data = std::string((char*)result->data, result->size);
auto json = nlohmann::json::parse(data);
std::string date = json["commit"]["commit"]["commiter"]["date"];

return date;
}

static bool is_newer_date(const std::string& date_old, const std::string& date_new)
{
std::istringstream ss_old(date_old);
std::istringstream ss_new(date_new);

std::tm timeinfo_old = {};
std::tm timeinfo_new = {};

ss_old >> std::get_time(&timeinfo_old, "%Y-%m-%dT%H:%M:%SZ");
ss_new >> std::get_time(&timeinfo_new, "%Y-%m-%dT%H:%M:%SZ");

std::time_t time_old = std::mktime(&timeinfo_old);
std::time_t time_new = std::mktime(&timeinfo_new);

return time_new > time_old;
}
};

} // namespace hydra
3 changes: 3 additions & 0 deletions qt/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <QApplication>
#include <QSurfaceFormat>
#include <settings.hxx>
#include <update.hxx>

// TODO: Not my favorite of designs
std::vector<EmulatorInfo> Settings::CoreInfo;
Expand All @@ -14,6 +15,8 @@ bool Settings::core_info_initialized_ = false;

int main(int argc, char* argv[])
{
hydra::Updater::UpdateDatabase();

auto settings_path = Settings::GetSavePath() / "settings.json";
Settings::Open(settings_path);
Settings::InitCoreInfo();
Expand Down
6 changes: 2 additions & 4 deletions qt/mainwindow.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,8 @@ void MainWindow::open_file_impl(const std::string& path)
}
}

if (!paused_)
{
emulator_timer_->start();
}
paused_ = false;
emulator_timer_->start();
}

void MainWindow::open_settings()
Expand Down
22 changes: 22 additions & 0 deletions vendored/miniz/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright 2013-2014 RAD Game Tools and Valve Software
Copyright 2010-2014 Rich Geldreich and Tenacious Software LLC

All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
Loading

0 comments on commit abbbd18

Please sign in to comment.