Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
khronokernel committed Apr 13, 2023
2 parents 1fe710f + 472c494 commit 980142c
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 4 deletions.
4 changes: 3 additions & 1 deletion .github/workflows/build-app-wxpython.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ jobs:
commitdate: ${{ github.event.head_commit.timestamp }}${{ github.event.release.published_at }}
MAC_NOTARIZATION_USERNAME: ${{ secrets.MAC_NOTARIZATION_USERNAME }}
MAC_NOTARIZATION_PASSWORD: ${{ secrets.MAC_NOTARIZATION_PASSWORD }}
ANALYTICS_KEY: ${{ secrets.ANALYTICS_KEY }}
ANALYTICS_SITE: ${{ secrets.ANALYTICS_SITE }}

steps:
- uses: actions/checkout@v3
- run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 Build-Binary.command --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}"
- run: /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 Build-Binary.command --reset_binaries --branch "${{ env.branch }}" --commit "${{ env.commiturl }}" --commit_date "${{ env.commitdate }}" --key "${{ env.ANALYTICS_KEY }}" --site "${{ env.ANALYTICS_SITE }}"
- run: 'codesign -s "Developer ID Application: Mykola Grymalyuk (S74BDJXQMD)" -v --force --deep --timestamp --entitlements ./payloads/entitlements.plist -o runtime "dist/OpenCore-Patcher.app"'
- run: cd dist; ditto -c -k --sequesterRsrc --keepParent OpenCore-Patcher.app ../OpenCore-Patcher-wxPython.app.zip
- run: xcrun altool --notarize-app --primary-bundle-id "com.dortania.opencore-legacy-patcher" --username "${{ env.MAC_NOTARIZATION_USERNAME }}" --password "${{ env.MAC_NOTARIZATION_PASSWORD }}" --file OpenCore-Patcher-wxPython.app.zip
Expand Down
70 changes: 70 additions & 0 deletions Build-Binary.command
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ class CreateBinary:
parser.add_argument('--commit', type=str, help='Git commit URL')
parser.add_argument('--commit_date', type=str, help='Git commit date')
parser.add_argument('--reset_binaries', action='store_true', help='Force redownload and imaging of payloads')
parser.add_argument('--key', type=str, help='Developer key for signing')
parser.add_argument('--site', type=str, help='Path to server')
args = parser.parse_args()
return args

Expand Down Expand Up @@ -132,17 +134,85 @@ class CreateBinary:
print(rm_output.stderr.decode('utf-8'))
raise Exception("Remove failed")

self._embed_key()

print("- Building GUI binary...")
build_args = [self.pyinstaller_path, "./OpenCore-Patcher-GUI.spec", "--noconfirm"]

build_result = subprocess.run(build_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

self._strip_key()

if build_result.returncode != 0:
print("- Build failed")
print(build_result.stderr.decode('utf-8'))
raise Exception("Build failed")




def _embed_key(self):
"""
Embed developer key into binary
"""

if not self.args.key:
print("- No developer key provided, skipping...")
return
if not self.args.site:
print("- No site provided, skipping...")
return

print("- Embedding developer key...")
if not Path("./resources/analytics_handler.py").exists():
print("- analytics_handler.py not found")
return

lines = []
with open("./resources/analytics_handler.py", "r") as f:
lines = f.readlines()

for i, line in enumerate(lines):
if line.startswith("SITE_KEY: str = "):
lines[i] = f"SITE_KEY: str = \"{self.args.key}\"\n"
elif line.startswith("ANALYTICS_SERVER: str = "):
lines[i] = f"ANALYTICS_SERVER: str = \"{self.args.site}\"\n"

with open("./resources/analytics_handler.py", "w") as f:
f.writelines(lines)


def _strip_key(self):
"""
Strip developer key from binary
"""

if not self.args.key:
print("- No developer key provided, skipping...")
return
if not self.args.site:
print("- No site provided, skipping...")
return

print("- Stripping developer key...")
if not Path("./resources/analytics_handler.py").exists():
print("- analytics_handler.py not found")
return

lines = []
with open("./resources/analytics_handler.py", "r") as f:
lines = f.readlines()

for i, line in enumerate(lines):
if line.startswith("SITE_KEY: str = "):
lines[i] = f"SITE_KEY: str = \"\"\n"
elif line.startswith("ANALYTICS_SERVER: str = "):
lines[i] = f"ANALYTICS_SERVER: str = \"\"\n"

with open("./resources/analytics_handler.py", "w") as f:
f.writelines(lines)


def _delete_extra_binaries(self):
"""
Delete extra binaries from payloads directory
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# OpenCore Legacy Patcher changelog

## 0.6.4
- Backend changes:
- Implement new analytics_handler.py module
- Adds support for anonymous analytics including host info (and crash reports in the future)
- Can be disabled via GUI or `defaults write com.dortania.opencore-legacy-patcher DisableCrashAndAnalyticsReporting -bool true`

## 0.6.3
- Update non-Metal Binaries:
Expand Down
25 changes: 25 additions & 0 deletions PRIVACY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Privacy Policy

OpenCore Legacy Patcher may collect pseudo-anonymized data about the host system and the OpenCore Legacy Patcher application. This data is used to improve the project and to help diagnose issues. The data collected is as follows:

* System's UUID as a SHA1 hash
* This is used to identify the system and to prevent duplicate reports
* Cannot be used to identify the system without the user providing the UUID
* Application name and version
* System's OS version
* System's model name, GPUs present and firmware vendor
* May include more hardware information in the future (ex. CPU, WiFi, etc)
* General country code of system's reported region
* ex. `US`, `CA`, etc

Identifiable data such as IP addresses, MAC addresses, serial numbers, etc. are not collected.

In the future, crash logs may also be collected to help with diagnosing issues.
----------

Users who wish to opt-out can do so either via the application's preferences or via the following command:
```
defaults write com.dortania.opencore-legacy-patcher DisableCrashAndAnalyticsReporting -bool true
```

To have your data removed, please contact us via our [Discord server](https://discord.gg/rqdPgH8xSN) and provide the UUID of your system.
97 changes: 97 additions & 0 deletions resources/analytics_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import datetime
import plistlib
from pathlib import Path
import json

from resources import network_handler, constants, global_settings


DATE_FORMAT: str = "%Y-%m-%d %H-%M-%S"
ANALYTICS_SERVER: str = ""
SITE_KEY: str = ""

VALID_ENTRIES: dict = {
'KEY': str, # Prevent abuse (embedded at compile time)
'UNIQUE_IDENTITY': str, # Host's UUID as SHA1 hash
'APPLICATION_NAME': str, # ex. OpenCore Legacy Patcher
'APPLICATION_VERSION': str, # ex. 0.2.0
'OS_VERSION': str, # ex. 10.15.7
'MODEL': str, # ex. MacBookPro11,5
'GPUS': list, # ex. ['Intel Iris Pro', 'AMD Radeon R9 M370X']
'FIRMWARE': str, # ex. APPLE
'LOCATION': str, # ex. 'US' (just broad region, don't need to be specific)
'TIMESTAMP': datetime.datetime, # ex. 2021-09-01-12-00-00
}


class Analytics:

def __init__(self, global_constants: constants.Constants) -> None:
self.constants: constants.Constants = global_constants

if global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting") is True:
return

self._generate_base_data()
self._post_data()


def _get_country(self) -> str:
# Get approximate country from .GlobalPreferences.plist
path = "/Library/Preferences/.GlobalPreferences.plist"
if not Path(path).exists():
return "US"

try:
result = plistlib.load(Path(path).open("rb"))
except:
return "US"

if "Country" not in result:
return "US"

return result["Country"]


def _generate_base_data(self) -> None:

self.unique_identity = str(self.constants.computer.uuid_sha1)
self.application = str("OpenCore Legacy Patcher")
self.version = str(self.constants.patcher_version)
self.os = str( self.constants.detected_os_version)
self.model = str(self.constants.computer.real_model)
self.gpus = []

self.firmware = str(self.constants.computer.firmware_vendor)
self.location = str(self._get_country())

for gpu in self.constants.computer.gpus:
self.gpus.append(str(gpu.arch))

self.data = {
'KEY': SITE_KEY,
'UNIQUE_IDENTITY': self.unique_identity,
'APPLICATION_NAME': self.application,
'APPLICATION_VERSION': self.version,
'OS_VERSION': self.os,
'MODEL': self.model,
'GPUS': self.gpus,
'FIRMWARE': self.firmware,
'LOCATION': self.location,
'TIMESTAMP': str(datetime.datetime.now().strftime(DATE_FORMAT)),
}

# convert to JSON:
self.data = json.dumps(self.data)


def _post_data(self) -> None:
# Post data to analytics server
if ANALYTICS_SERVER == "":
return
if SITE_KEY == "":
return
network_handler.NetworkUtilities().post(ANALYTICS_SERVER, json = self.data)



4 changes: 4 additions & 0 deletions resources/device_probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import itertools
import subprocess
import plistlib
import hashlib
from pathlib import Path
from dataclasses import dataclass, field
from typing import Any, ClassVar, Optional, Type, Union
Expand Down Expand Up @@ -491,6 +492,7 @@ class Computer:
reported_model: Optional[str] = None
reported_board_id: Optional[str] = None
build_model: Optional[str] = None
uuid_sha1: Optional[str] = None
gpus: list[GPU] = field(default_factory=list)
igpu: Optional[GPU] = None # Shortcut for IGPU
dgpu: Optional[GPU] = None # Shortcut for GFX0
Expand Down Expand Up @@ -719,6 +721,8 @@ def smbios_probe(self):
else:
board = "board-id"
self.reported_board_id = ioreg.corefoundation_to_native(ioreg.IORegistryEntryCreateCFProperty(entry, board, ioreg.kCFAllocatorDefault, ioreg.kNilOptions)).strip(b"\0").decode() # type: ignore
self.uuid_sha1 = ioreg.corefoundation_to_native(ioreg.IORegistryEntryCreateCFProperty(entry, "IOPlatformUUID", ioreg.kCFAllocatorDefault, ioreg.kNilOptions)) # type: ignore
self.uuid_sha1 = hashlib.sha1(self.uuid_sha1.encode()).hexdigest()
ioreg.IOObjectRelease(entry)

# Real model
Expand Down
18 changes: 16 additions & 2 deletions resources/gui/gui_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2980,12 +2980,23 @@ def dev_settings_menu(self, event=None):
self.delete_unused_kdks_checkbox.GetPosition().y + self.delete_unused_kdks_checkbox.GetSize().height))
self.set_ignore_app_updates_checkbox.SetToolTip(wx.ToolTip("This will set whether OpenCore will ignore App Updates on launch.\nEnable this option if you do not want to be prompted for App Updates"))

# Set Disable Analytics
res = global_settings.GlobalEnviromentSettings().read_property("DisableCrashAndAnalyticsReporting")
res = False if res is None else res
self.set_disable_analytics_checkbox = wx.CheckBox(self.frame_modal, label="Disable Crash/Analytics")
self.set_disable_analytics_checkbox.SetValue(res)
self.set_disable_analytics_checkbox.Bind(wx.EVT_CHECKBOX, self.set_disable_analytics_click)
self.set_disable_analytics_checkbox.SetPosition(wx.Point(
self.set_ignore_app_updates_checkbox.GetPosition().x,
self.set_ignore_app_updates_checkbox.GetPosition().y + self.set_ignore_app_updates_checkbox.GetSize().height))
self.set_disable_analytics_checkbox.SetToolTip(wx.ToolTip("Sets whether anonymized analytics are sent to the Dortania team.\nThis is used to help improve the application and is completely optional."))

# Button: Developer Debug Info
self.debug_button = wx.Button(self.frame_modal, label="Developer Debug Info")
self.debug_button.Bind(wx.EVT_BUTTON, self.additional_info_menu)
self.debug_button.SetPosition(wx.Point(
self.set_ignore_app_updates_checkbox.GetPosition().x,
self.set_ignore_app_updates_checkbox.GetPosition().y + self.set_ignore_app_updates_checkbox.GetSize().height + 5))
self.set_disable_analytics_checkbox.GetPosition().x,
self.set_disable_analytics_checkbox.GetPosition().y + self.set_disable_analytics_checkbox.GetSize().height + 5))
self.debug_button.Center(wx.HORIZONTAL)

# Button: return to main menu
Expand Down Expand Up @@ -3038,6 +3049,9 @@ def set_ignore_app_updates_click(self, event):
else:
global_settings.GlobalEnviromentSettings().write_property("IgnoreAppUpdates", False)

def set_disable_analytics_click(self, event):
global_settings.GlobalEnviromentSettings().write_property("DisableCrashAndAnalyticsReporting", self.set_disable_analytics_checkbox.GetValue())

def firewire_click(self, event=None):
if self.firewire_boot_checkbox.GetValue():
logging.info("Firewire Enabled")
Expand Down
4 changes: 3 additions & 1 deletion resources/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
arguments,
reroute_payloads,
commit_info,
logging_handler
logging_handler,
analytics_handler,
)


Expand Down Expand Up @@ -89,6 +90,7 @@ def _generate_base_data(self) -> None:

# Generate defaults
defaults.GenerateDefaults(self.computer.real_model, True, self.constants)
threading.Thread(target=analytics_handler.Analytics, args=(self.constants,)).start()

if utilities.check_cli_args() is None:
logging.info(f"- No arguments present, loading {'GUI' if self.constants.wxpython_variant is True else 'TUI'} mode")
Expand Down

0 comments on commit 980142c

Please sign in to comment.