1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-02-01 03:05:24 +01:00

Merge pull request #4790 from IntelOrca/add/uri-protocol-handler

Add uri protocol handler
This commit is contained in:
Ted John
2017-03-10 10:46:28 +00:00
committed by GitHub
14 changed files with 361 additions and 25 deletions

View File

@@ -240,6 +240,7 @@
C6E96E321E04072F0076A04F /* TitleSequencePlayer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C6E96E2D1E04072F0076A04F /* TitleSequencePlayer.cpp */; };
C6E96E361E0408B40076A04F /* libzip.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = C6E96E351E0408B40076A04F /* libzip.dylib */; };
C6E96E371E040E040076A04F /* libzip.dylib in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = C6E96E351E0408B40076A04F /* libzip.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
C6EABCC41E719691008C09AB /* UriHandler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C6EABCC31E719691008C09AB /* UriHandler.cpp */; };
D41B73EF1C2101890080A7B9 /* libcurl.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B73EE1C2101890080A7B9 /* libcurl.tbd */; };
D41B741D1C210A7A0080A7B9 /* libiconv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = D41B741C1C210A7A0080A7B9 /* libiconv.tbd */; };
D41B74731C2125E50080A7B9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D41B74721C2125E50080A7B9 /* Assets.xcassets */; };
@@ -716,6 +717,7 @@
C6E96E331E0408A80076A04F /* zip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zip.h; sourceTree = "<group>"; };
C6E96E341E0408A80076A04F /* zipconf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = zipconf.h; sourceTree = "<group>"; };
C6E96E351E0408B40076A04F /* libzip.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libzip.dylib; sourceTree = "<group>"; };
C6EABCC31E719691008C09AB /* UriHandler.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = UriHandler.cpp; sourceTree = "<group>"; };
C6FF1BAD1DBCE1A10078DCB5 /* junior_roller_coaster.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = junior_roller_coaster.h; sourceTree = "<group>"; };
D41B73EE1C2101890080A7B9 /* libcurl.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libcurl.tbd; path = usr/lib/libcurl.tbd; sourceTree = SDKROOT; };
D41B741C1C210A7A0080A7B9 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = usr/lib/libiconv.tbd; sourceTree = SDKROOT; };
@@ -1686,6 +1688,7 @@
D44270D91CC81B3200D84D28 /* RootCommands.cpp */,
D44270DA1CC81B3200D84D28 /* ScreenshotCommands.cpp */,
D44270DB1CC81B3200D84D28 /* SpriteCommands.cpp */,
C6EABCC31E719691008C09AB /* UriHandler.cpp */,
);
path = cmdline;
sourceTree = "<group>";
@@ -2866,6 +2869,7 @@
C686F8B91CDBC37E009F9BFC /* supports.c in Sources */,
D442726E1CC81B3200D84D28 /* maze_construction.c in Sources */,
D44272751CC81B3200D84D28 /* news_options.c in Sources */,
C6EABCC41E719691008C09AB /* UriHandler.cpp in Sources */,
D44272551CC81B3200D84D28 /* changelog.c in Sources */,
C686F9471CDBC3B7009F9BFC /* top_spin.c in Sources */,
D442720E1CC81B3200D84D28 /* rect.c in Sources */,

View File

@@ -14,16 +14,18 @@
*****************************************************************************/
#pragma endregion
#include <memory>
#include <string>
#include "core/Console.hpp"
#include "core/Guard.hpp"
#include "core/File.h"
#include "core/FileStream.hpp"
#include "core/Guard.hpp"
#include "core/String.hpp"
#include "FileClassifier.h"
#include "network/network.h"
#include "object/ObjectRepository.h"
#include "OpenRCT2.h"
#include "ParkImporter.h"
#include "platform/crash.h"
#include "PlatformEnvironment.h"
#include "ride/TrackDesignRepository.h"
@@ -518,50 +520,59 @@ namespace OpenRCT2
ClassifiedFile info;
if (TryClassifyFile(path, &info))
{
if (info.Type == FILE_TYPE::SAVED_GAME)
if (info.Type == FILE_TYPE::SAVED_GAME ||
info.Type == FILE_TYPE::SCENARIO)
{
std::unique_ptr<IParkImporter> parkImporter;
if (info.Version <= 2)
{
if (rct1_load_saved_game(path))
parkImporter.reset(ParkImporter::CreateS4());
}
else
{
parkImporter.reset(ParkImporter::CreateS6());
}
if (info.Type == FILE_TYPE::SAVED_GAME)
{
try
{
parkImporter->LoadSavedGame(path);
parkImporter->Import();
game_fix_save_vars();
sprite_position_tween_reset();
gScreenAge = 0;
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
game_load_init();
return true;
}
catch (const Exception &)
{
Console::Error::WriteLine("Error loading saved game.");
}
}
else
{
if (game_load_save(path))
{
gFirstTimeSave = 0;
return true;
}
}
Console::Error::WriteLine("Error loading saved game.");
}
else if (info.Type == FILE_TYPE::SCENARIO)
{
if (info.Version <= 2)
{
if (rct1_load_scenario(path))
try
{
parkImporter->LoadScenario(path);
parkImporter->Import();
game_fix_save_vars();
sprite_position_tween_reset();
gScreenAge = 0;
gLastAutoSaveUpdate = AUTOSAVE_PAUSE;
scenario_begin();
return true;
}
}
else
{
if (scenario_load_and_play_from_path(path))
catch (const Exception &)
{
return true;
Console::Error::WriteLine("Error loading scenario.");
}
}
Console::Error::WriteLine("Error loading scenario.");
}
else
{
Console::Error::WriteLine("Invalid file type.");
Console::Error::WriteLine("Invalid file type.");
}
}
else

View File

@@ -110,4 +110,5 @@ namespace CommandLine
exitcode_t HandleCommandDefault();
exitcode_t HandleCommandConvert(CommandLineArgEnumerator * enumerator);
exitcode_t HandleCommandUri(CommandLineArgEnumerator * enumerator);
}

View File

@@ -126,6 +126,7 @@ const CommandLineCommand CommandLine::RootCommands[]
DefineCommand("set-rct2", "<path>", StandardOptions, HandleCommandSetRCT2),
DefineCommand("convert", "<source> <destination>", StandardOptions, CommandLine::HandleCommandConvert),
DefineCommand("scan-objects", "<path>", StandardOptions, HandleCommandScanObjects),
DefineCommand("handle-uri", "openrct2://.../", StandardOptions, CommandLine::HandleCommandUri),
#if defined(__WINDOWS__) && !defined(__MINGW32__)
DefineCommand("register-shell", "", RegisterShellOptions, HandleCommandRegisterShell),

View File

@@ -0,0 +1,108 @@
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
/*****************************************************************************
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
*
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
* For more information, visit https://github.com/OpenRCT2/OpenRCT2
*
* OpenRCT2 is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* A full copy of the GNU General Public License can be found in licence.txt
*****************************************************************************/
#pragma endregion
#include "../core/Console.hpp"
#include "../core/Memory.hpp"
#include "../core/String.hpp"
#include "../network/network.h"
#include "../OpenRCT2.h"
#include "CommandLine.hpp"
static exitcode_t HandleUri(const std::string &uri);
#ifndef DISABLE_NETWORK
static exitcode_t HandleUriJoin(const std::vector<std::string> &args);
static bool TryParseHostnamePort(const std::string &hostnamePort, std::string * outHostname, sint32 * outPort, sint32 defaultPort);
#endif
exitcode_t CommandLine::HandleCommandUri(CommandLineArgEnumerator * enumerator)
{
const utf8 * uri;
if (enumerator->TryPopString(&uri))
{
if (String::StartsWith(uri, "openrct2://"))
{
const utf8 * uriCommand = uri + 11;
return HandleUri(uriCommand);
}
}
Console::Error::WriteLine("Invalid URI");
return EXITCODE_FAIL;
}
static exitcode_t HandleUri(const std::string &uri)
{
exitcode_t result = EXITCODE_CONTINUE;
auto args = String::Split(uri, "/");
if (args.size() > 0)
{
#ifndef DISABLE_NETWORK
std::string arg = args[0];
if (arg == "join")
{
result = HandleUriJoin(args);
}
#endif
}
return result;
}
#ifndef DISABLE_NETWORK
static exitcode_t HandleUriJoin(const std::vector<std::string> &args)
{
std::string hostname;
sint32 port;
if (args.size() > 1 && TryParseHostnamePort(args[1], &hostname, &port, NETWORK_DEFAULT_PORT))
{
// Set the network start configuration
gNetworkStart = NETWORK_MODE_CLIENT;
String::Set(gNetworkStartHost, sizeof(gNetworkStartHost), hostname.c_str());
gNetworkStartPort = port;
return EXITCODE_CONTINUE;
}
else
{
Console::Error::WriteLine("Expected hostname:port after join");
return EXITCODE_FAIL;
}
}
static bool TryParseHostnamePort(const std::string &hostnamePort, std::string * outHostname, sint32 * outPort, sint32 defaultPort)
{
try
{
// Argument is in hostname:port format, so we need to split
std::string hostname = hostnamePort;
sint32 port = defaultPort;
size_t colonIndex = hostnamePort.find_first_of(':');
if (colonIndex != std::string::npos)
{
hostname = hostnamePort.substr(0, colonIndex);
port = std::stoi(hostnamePort.substr(colonIndex + 1));
}
*outPort = port;
*outHostname = hostname;
return true;
}
catch (const std::exception &)
{
return false;
}
}
#endif // DISABLE_NETWORK

View File

@@ -113,6 +113,9 @@ typedef uint8 colour_t;
#endif // __GNUC__
#endif // __cplusplus
// Gets the name of a symbol as a C string
#define nameof(symbol) #symbol
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
#include <unistd.h>
#define STUB() log_warning("Function %s at %s:%d is a stub.\n", __PRETTY_FUNCTION__, __FILE__, __LINE__)

View File

@@ -15,6 +15,8 @@
#pragma endregion
#include <cwctype>
#include <stdexcept>
#include <vector>
extern "C"
{
@@ -116,6 +118,19 @@ namespace String
}
}
size_t IndexOf(const utf8 * str, utf8 match, size_t startIndex)
{
const utf8 * ch = str + startIndex;
for (; *ch != '\0'; ch++)
{
if (*ch == match)
{
return (size_t)(ch - str);
}
}
return SIZE_MAX;
}
size_t LastIndexOf(const utf8 * str, utf8 match)
{
const utf8 * lastOccurance = nullptr;
@@ -300,6 +315,78 @@ namespace String
return DiscardUse(ptr, String::Duplicate(replacement));
}
utf8 * Substring(const utf8 * buffer, size_t index)
{
size_t bufferSize = String::SizeOf(buffer);
bool goodSubstring = index <= bufferSize;
Guard::Assert(goodSubstring, "Substring past end of input string.");
// If assertion continues, return empty string to avoid crash
if (!goodSubstring)
{
return String::Duplicate("");
}
return String::Duplicate(buffer + index);
}
utf8 * Substring(const utf8 * buffer, size_t index, size_t size)
{
size_t bufferSize = String::SizeOf(buffer);
bool goodSubstring = index + size <= bufferSize;
Guard::Assert(goodSubstring, "Substring past end of input string.");
// If assertion continues, cap the substring to avoid crash
if (!goodSubstring)
{
if (index >= bufferSize)
{
size = 0;
}
else
{
size = bufferSize - index;
}
}
utf8 * result = Memory::Allocate<utf8>(size + 1);
Memory::Copy(result, buffer + index, size);
result[size] = '\0';
return result;
}
std::vector<std::string> Split(const std::string &s, const std::string &delimiter)
{
if (delimiter.empty())
{
throw std::invalid_argument(nameof(delimiter) " can not be empty.");
}
std::vector<std::string> results;
if (!s.empty())
{
size_t index = 0;
size_t nextIndex;
do
{
nextIndex = s.find(delimiter, index);
std::string value;
if (nextIndex == std::string::npos)
{
value = s.substr(index);
}
else
{
value = s.substr(index, nextIndex - index);
}
results.push_back(value);
index = nextIndex + delimiter.size();
}
while (nextIndex != SIZE_MAX);
}
return results;
}
utf8 * SkipBOM(utf8 * buffer)
{
return (utf8*)SkipBOM((const utf8 *)buffer);

View File

@@ -17,6 +17,7 @@
#pragma once
#include <string>
#include <vector>
#include "../common.h"
namespace String
@@ -32,6 +33,7 @@ namespace String
bool Equals(const std::string &a, const std::string &b, bool ignoreCase = false);
bool Equals(const utf8 * a, const utf8 * b, bool ignoreCase = false);
bool StartsWith(const utf8 * str, const utf8 * match, bool ignoreCase = false);
size_t IndexOf(const utf8 * str, utf8 match, size_t startIndex = 0);
size_t LastIndexOf(const utf8 * str, utf8 match);
/**
@@ -64,6 +66,22 @@ namespace String
*/
utf8 * DiscardDuplicate(utf8 * * ptr, const utf8 * replacement);
/**
* Creates a new string containing the characters between index and and end of the input string.
*/
utf8 * Substring(const utf8 * buffer, size_t index);
/**
* Creates a new string containing the characters between index and index + size of the input string.
*/
utf8 * Substring(const utf8 * buffer, size_t index, size_t size);
/**
* Splits the given string by a delimiter and returns the values as a new string array.
* @returns the number of values.
*/
std::vector<std::string> Split(const std::string &s, const std::string &delimiter);
utf8 * SkipBOM(utf8 * buffer);
const utf8 * SkipBOM(const utf8 * buffer);

View File

@@ -72,6 +72,7 @@
<ClCompile Include="audio\FileAudioSource.cpp" />
<ClCompile Include="audio\MemoryAudioSource.cpp" />
<ClCompile Include="audio\NullAudioSource.cpp" />
<ClCompile Include="cmdline\UriHandler.cpp" />
<ClCompile Include="config\Config.cpp" />
<ClCompile Include="config\IniReader.cpp" />
<ClCompile Include="config\IniWriter.cpp" />

View File

@@ -224,6 +224,7 @@ void core_init();
HWND windows_get_window_handle();
void platform_setup_file_associations();
void platform_remove_file_associations();
bool platform_setup_uri_protocol();
// This function cannot be marked as 'static', even though it may seem to be,
// as it requires external linkage, which 'static' prevents
__declspec(dllexport) sint32 StartOpenRCT(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, sint32 nCmdShow);

View File

@@ -1040,7 +1040,8 @@ utf8* platform_get_username() {
// File association setup
///////////////////////////////////////////////////////////////////////////////
#define SOFTWARE_CLASSES L"Software\\Classes"
#define SOFTWARE_CLASSES L"Software\\Classes"
#define MUI_CACHE L"Local Settings\\Software\\Microsoft\\Windows\\Shell\\MuiCache"
static void get_progIdName(wchar_t *dst, const utf8 *extension)
{
@@ -1183,4 +1184,57 @@ void platform_remove_file_associations()
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
// URI protocol association setup
///////////////////////////////////////////////////////////////////////////////
bool platform_setup_uri_protocol()
{
log_verbose("Setting up URI protocol...");
// [HKEY_CURRENT_USER\Software\Classes]
HKEY hRootKey;
if (RegOpenKeyW(HKEY_CURRENT_USER, SOFTWARE_CLASSES, &hRootKey) == ERROR_SUCCESS) {
// [hRootKey\openrct2]
HKEY hClassKey;
if (RegCreateKeyA(hRootKey, "openrct2", &hClassKey) == ERROR_SUCCESS) {
if (RegSetValueA(hClassKey, NULL, REG_SZ, "URL:openrct2", 0) == ERROR_SUCCESS) {
if (RegSetKeyValueA(hClassKey, NULL, "URL Protocol", REG_SZ, "", 0) == ERROR_SUCCESS) {
// [hRootKey\openrct2\shell\open\command]
wchar_t exePath[MAX_PATH];
GetModuleFileNameW(NULL, exePath, MAX_PATH);
wchar_t buffer[512];
swprintf_s(buffer, sizeof(buffer), L"\"%s\" handle-uri \"%%1\"", exePath);
if (RegSetValueW(hClassKey, L"shell\\open\\command", REG_SZ, buffer, 0) == ERROR_SUCCESS) {
// Not compulsory, but gives the application a nicer name
// [HKEY_CURRENT_USER\SOFTWARE\Classes\Local Settings\Software\Microsoft\Windows\Shell\MuiCache]
HKEY hMuiCacheKey;
if (RegCreateKeyW(hRootKey, MUI_CACHE, &hMuiCacheKey) == ERROR_SUCCESS) {
swprintf_s(buffer, sizeof(buffer), L"%s.FriendlyAppName", exePath);
#ifdef __MINGW32__
// mingw-w64 defines RegSetKeyValueW's signature incorrectly
// A fix has already been submitted upstream, this can be be removed after their next release:
// https://sourceforge.net/p/mingw-w64/mingw-w64/ci/da9341980a4b70be3563ac09b5927539e7da21f7/
RegSetKeyValueW(hMuiCacheKey, NULL, (LPCSTR)buffer, REG_SZ, (LPCSTR)L"OpenRCT2", sizeof(L"OpenRCT2") + 1);
#else
RegSetKeyValueW(hMuiCacheKey, NULL, buffer, REG_SZ, L"OpenRCT2", sizeof(L"OpenRCT2") + 1);
#endif
}
log_verbose("URI protocol setup successful");
return true;
}
}
}
}
}
log_verbose("URI protocol setup failed");
return false;
}
///////////////////////////////////////////////////////////////////////////////
#endif

View File

@@ -0,0 +1,25 @@
#pragma once
#include <initializer_list>
#include <vector>
#include <gtest/gtest.h>
template<typename T, typename TExpected>
void AssertVector(std::vector<T> actual, TExpected expected)
{
ASSERT_EQ(actual.size(), expected.size()) <<
"Expected vector of size " << expected.size() << ", but was " << actual.size();
size_t i = 0;
for (auto item : expected)
{
EXPECT_EQ(actual[i], item) <<
"Element at index " << i << " did not match";
i++;
}
}
template<typename T>
void AssertVector(std::vector<T> actual, std::initializer_list<T> expected)
{
AssertVector<T, std::initializer_list<T>>(actual, expected);
}

View File

@@ -2,6 +2,7 @@
#include <tuple>
#include <gtest/gtest.h>
#include <openrct2/core/String.hpp>
#include "AssertHelpers.hpp"
using TCase = std::tuple<std::string, std::string>;
@@ -28,3 +29,23 @@ TEST_P(StringTest, Trim)
std::string actual = String::Trim(input);
ASSERT_EQ(expected, actual);
}
TEST_F(StringTest, Split_ByComma)
{
auto actual = String::Split("a,bb,ccc,dd", ",");
AssertVector<std::string>(actual, { "a", "bb", "ccc", "dd" });
}
TEST_F(StringTest, Split_ByColonColon)
{
auto actual = String::Split("a::bb:ccc:::::dd", "::");
AssertVector<std::string>(actual, { "a", "bb:ccc", "", ":dd" });
}
TEST_F(StringTest, Split_Empty)
{
auto actual = String::Split("", ".");
AssertVector<std::string>(actual, { });
}
TEST_F(StringTest, Split_ByEmpty)
{
EXPECT_THROW(String::Split("string", ""), std::invalid_argument);
}

View File

@@ -47,6 +47,7 @@
</ItemDefinitionGroup>
<!-- Files -->
<ItemGroup>
<ClInclude Include="AssertHelpers.hpp" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="LanguagePackTest.cpp" />