Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CREDITS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Contributors

- Peter Sharma and contributors: application and controller protocol implementation.
- [Peter Sharma and contributors](https://github.com/Peterksharma/switch2mac): application and controller protocol implementation.
- Andrei-Kondrykau: browser integration.
- vialoh: RetroArch integration.
- ndeadly, Nadeflore, coffincolors, trevlars, darthcloud, and the controller research community: protocol research.
- Sam Lantinga and the SDL contributors: SDL.

License notices: [LICENSES/README.md](LICENSES/README.md).
9 changes: 9 additions & 0 deletions Integrations/CMake/Bundle.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,22 @@ function(switch2kit_embed target)
set_property(TARGET "${target}" PROPERTY MACOSX_BUNDLE_INFO_PLIST "${_prepared}")
endif()

get_filename_component(_notice_root "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../.." ABSOLUTE)
file(GLOB _notices CONFIGURE_DEPENDS "${_notice_root}/LICENSES/*")
set_property(TARGET "${target}" APPEND PROPERTY LINK_DEPENDS "${_notice_root}/CREDITS.md" ${_notices})
add_custom_command(TARGET "${target}" POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory "$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Frameworks"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "$<TARGET_FILE:Switch2Kit::C>"
"$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Frameworks/"
COMMAND "${CMAKE_COMMAND}"
"-DS2K_BUNDLE_LIBRARY=$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Frameworks/$<TARGET_FILE_NAME:Switch2Kit::C>"
-P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/PrepareBundle.cmake"
COMMAND "${CMAKE_COMMAND}" -E make_directory
"$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Resources/Switch2KitNotices"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${_notice_root}/CREDITS.md"
"$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Resources/Switch2KitNotices/CREDITS.md"
COMMAND "${CMAKE_COMMAND}" -E copy_directory "${_notice_root}/LICENSES"
"$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Resources/Switch2KitNotices/LICENSES"
COMMAND xcrun swift-stdlib-tool --copy --platform macosx
--scan-executable "$<TARGET_FILE:Switch2Kit::C>"
--destination "$<TARGET_BUNDLE_CONTENT_DIR:${target}>/Frameworks"
Expand Down
21 changes: 21 additions & 0 deletions LICENSES/MIT-trevlars.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 trevlars

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.
8 changes: 8 additions & 0 deletions LICENSES/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Third-party notices

Switch2Kit includes code from [Peterksharma/switch2mac](https://github.com/Peterksharma/switch2mac/tree/ea6719f0a1d6b6986c00aca9ed4169a85c8cc9ae). Contributors are listed in [CREDITS.md](../CREDITS.md).

- [MIT-trevlars.txt](MIT-trevlars.txt): controller reference implementation, [trevlars/switch2-controllers-linux](https://github.com/trevlars/switch2-controllers-linux).
- [SDL-zlib.txt](SDL-zlib.txt): SDL-derived patches, [SDL 3.4.14](https://github.com/libsdl-org/SDL/tree/147a8ee32dbf9ac02f3794964490687b6bbda1bc). These are modified, unofficial SDL sources.

Dolphin and Cemu retain their upstream copyright and license notices.
17 changes: 17 additions & 0 deletions LICENSES/SDL-zlib.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Copyright (C) 1997-2026 Sam Lantinga <slouken@libsdl.org>

This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.

Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:

1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
2 changes: 1 addition & 1 deletion Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Switch2Kit talks to your Nintendo Switch 2 controllers over Bluetooth so games on this Mac can use them.</string>
<key>NSHumanReadableCopyright</key>
<string>© 2026 Peter Sharma. Protocol research by the Switch 2 controller community.</string>
<string>© 2026 Peter Sharma and Switch2Kit contributors.</string>
</dict>
</plist>
7 changes: 6 additions & 1 deletion scripts/build-app.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ if [ -f Resources/AppIcon.icns ]; then
"$OUT/Contents/Info.plist" 2>/dev/null || true
fi

# Keep the compact source notices in the bundle before any signature is applied.
mkdir -p "$OUT/Contents/Resources/Switch2KitNotices"
cp CREDITS.md "$OUT/Contents/Resources/Switch2KitNotices/"
cp -R LICENSES "$OUT/Contents/Resources/Switch2KitNotices/"

# Keep setup resources with the binary; never install the extension automatically.
mkdir -p "$OUT/Contents/Resources/BrowserExtension"
cp browser/extension/manifest.json browser/extension/*.js "$OUT/Contents/Resources/BrowserExtension/"
REVISION=$(git rev-parse HEAD)
DIRTY=false
[ -z "$(git status --porcelain --untracked-files=normal -- Sources Resources browser/extension scripts Package.swift)" ] || DIRTY=true
[ -z "$(git status --porcelain --untracked-files=normal -- Sources Resources browser/extension scripts Package.swift CREDITS.md LICENSES)" ] || DIRTY=true
/usr/libexec/PlistBuddy -c "Add :Switch2KitSourceRevision string $REVISION" "$OUT/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Add :Switch2KitSourceDirty bool $DIRTY" "$OUT/Contents/Info.plist"
/usr/libexec/PlistBuddy -c "Add :Switch2KitBuildArchitecture string $(uname -m)" "$OUT/Contents/Info.plist"
Expand Down
3 changes: 3 additions & 0 deletions scripts/build-switch2kit-c.sh
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ lipo "$WORK/consumer" -verify_arch arm64 x86_64
"$WORK/consumer"
xcodebuild -create-xcframework -library "$LIB" \
-headers "$ROOT/Sources/Switch2KitCABI/include" -output "$WORK/Switch2KitC.xcframework"
mkdir -p "$WORK/Switch2KitC.xcframework/Notices"
cp "$ROOT/CREDITS.md" "$WORK/Switch2KitC.xcframework/Notices/"
cp -R "$ROOT/LICENSES" "$WORK/Switch2KitC.xcframework/Notices/"
plutil -lint "$WORK/Switch2KitC.xcframework/Info.plist"
python3 - "$WORK/Switch2KitC.xcframework/Info.plist" <<'PY'
import plistlib,sys
Expand Down
1 change: 1 addition & 0 deletions scripts/build-switch2kit-emulator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ case "$emulator" in
esac
cmake -S "$source" -B "$build" "${args[@]}" "$@"
cmake --build "$build" --target "$target" --parallel "${S2K_BUILD_JOBS:-4}"
python3 "$root/scripts/verify-distribution-notices.py" "$emulator" "$source" "$build"
52 changes: 52 additions & 0 deletions scripts/verify-distribution-notices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env python3
"""Check bundled source notices and upstream Finder metadata."""
import argparse
import hashlib
import json
from pathlib import Path
import plistlib

ROOT = Path(__file__).resolve().parents[1]
NOTICES = ('CREDITS.md', 'LICENSES/README.md', 'LICENSES/MIT-trevlars.txt', 'LICENSES/SDL-zlib.txt')


def verify(app, emulator, root=ROOT):
info = plistlib.loads((app / 'Contents/Info.plist').read_bytes())
expected_id = {'dolphin': 'org.dolphin-emu.dolphin', 'cemu': 'info.cemu.Cemu'}[emulator]
if info.get('CFBundleIdentifier') != expected_id:
raise ValueError('Unexpected host bundle identifier')
notice = info.get('NSHumanReadableCopyright')
if not isinstance(notice, str) or not notice.strip() or not (
'GPL' in notice if emulator == 'dolphin' else 'Cemu' in notice):
raise ValueError('Missing or replaced upstream Finder copyright/license notice')
digests = {}
for name in NOTICES:
source = root / name
bundled = app / 'Contents/Resources/Switch2KitNotices' / name
if not bundled.is_file() or bundled.is_symlink() or bundled.read_bytes() != source.read_bytes():
raise ValueError('Missing or altered bundled notice: ' + name)
digests[name] = hashlib.sha256(source.read_bytes()).hexdigest()
return {'version': 1, 'emulator': emulator, 'copyright_notice': notice,
'notices': digests}


def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('emulator', choices=('dolphin', 'cemu'))
parser.add_argument('source', type=Path)
parser.add_argument('build', type=Path)
options = parser.parse_args()
report = options.build / 'integration-notices.json'
report.unlink(missing_ok=True)
directory = options.build / 'Binaries' if options.emulator == 'dolphin' else options.source / 'bin'
apps = [app for app in directory.glob('*.app')
if (app / 'Contents/Frameworks/libSwitch2KitC.dylib').is_file()]
if len(apps) != 1:
raise ValueError('Expected exactly one integrated application')
result = verify(apps[0], options.emulator)
report.write_text(json.dumps(result, indent=2) + '\n')
print('PASS bundled notices and upstream Finder metadata')


if __name__ == '__main__':
main()
3 changes: 3 additions & 0 deletions tests/distribution-notices/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/bash
set -euo pipefail
python3 "$(dirname "$0")/test_notices.py"
94 changes: 94 additions & 0 deletions tests/distribution-notices/test_notices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import importlib.util
import hashlib
from pathlib import Path
import plistlib
import tempfile
import unittest

ROOT = Path(__file__).resolve().parents[2]
spec = importlib.util.spec_from_file_location('notices', ROOT / 'scripts/verify-distribution-notices.py')
notices = importlib.util.module_from_spec(spec)
spec.loader.exec_module(notices)


class NoticesTests(unittest.TestCase):
def fixture(self, base, emulator):
app = base / 'Host with spaces.app'
for name in notices.NOTICES:
target = app / 'Contents/Resources/Switch2KitNotices' / name
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes((ROOT / name).read_bytes())
info = {'CFBundleIdentifier': 'org.dolphin-emu.dolphin' if emulator == 'dolphin' else 'info.cemu.Cemu',
'NSHumanReadableCopyright': 'Licensed under GPL version 2 or later (GPLv2+)' if emulator == 'dolphin'
else 'Copyright © 2026 Cemu Project'}
(app / 'Contents/Info.plist').write_bytes(plistlib.dumps(info))
return app, info

def test_both_upstream_notices_and_all_source_texts_are_retained(self):
for emulator in ('dolphin', 'cemu'):
with tempfile.TemporaryDirectory() as temporary:
app, info = self.fixture(Path(temporary), emulator)
result = notices.verify(app, emulator)
self.assertEqual(result['copyright_notice'], info['NSHumanReadableCopyright'])
self.assertEqual(set(result['notices']), set(notices.NOTICES))
self.assertEqual(result['emulator'], emulator)
for name, digest in result['notices'].items():
self.assertEqual(digest, hashlib.sha256((ROOT / name).read_bytes()).hexdigest())

def test_missing_empty_or_reassigned_copyright_fails(self):
for emulator in ('dolphin', 'cemu'):
for value in (None, '', ' ', 42, '© Switch2Kit'):
with self.subTest(emulator=emulator, value=value), tempfile.TemporaryDirectory() as temporary:
app, info = self.fixture(Path(temporary), emulator)
if value is None:
del info['NSHumanReadableCopyright']
else:
info['NSHumanReadableCopyright'] = value
(app / 'Contents/Info.plist').write_bytes(plistlib.dumps(info))
with self.assertRaises(ValueError):
notices.verify(app, emulator)

def test_missing_altered_or_symlinked_notice_fails(self):
for name in notices.NOTICES:
for defect in ('missing', 'changed', 'symlink'):
with self.subTest(name=name, defect=defect), tempfile.TemporaryDirectory() as temporary:
app, _ = self.fixture(Path(temporary), 'dolphin')
target = app / 'Contents/Resources/Switch2KitNotices' / name
target.unlink()
if defect == 'changed':
target.write_text('Not the retained notice')
elif defect == 'symlink':
target.symlink_to(ROOT / name)
with self.assertRaises(ValueError):
notices.verify(app, 'dolphin')

def test_emulator_identity_cannot_be_reassigned(self):
with tempfile.TemporaryDirectory() as temporary:
app, _ = self.fixture(Path(temporary), 'dolphin')
with self.assertRaises(ValueError):
notices.verify(app, 'cemu')

def test_packagers_copy_notices_before_signing_or_archiving(self):
app = (ROOT / 'scripts/build-app.sh').read_text()
self.assertLess(app.index('cp -R LICENSES'), app.index('codesign --force'))
native = (ROOT / 'scripts/build-switch2kit-c.sh').read_text()
self.assertLess(native.index('cp -R "$ROOT/LICENSES"'), native.index('ditto -c -k'))
bundle = (ROOT / 'Integrations/CMake/Bundle.cmake').read_text()
self.assertIn('LINK_DEPENDS "${_notice_root}/CREDITS.md"', bundle)
self.assertIn('copy_directory "${_notice_root}/LICENSES"', bundle)
self.assertNotIn('COMMAND codesign', bundle)
self.assertIn('verify-distribution-notices.py', (ROOT / 'scripts/build-switch2kit-emulator.sh').read_text())

def test_notice_index_links_sources_and_retained_texts(self):
text = (ROOT / 'LICENSES/README.md').read_text()
self.assertIn('https://github.com/Peterksharma/switch2mac/tree/', text)
self.assertIn('[CREDITS.md](../CREDITS.md)', text)
for name in ('MIT-trevlars.txt', 'SDL-zlib.txt'):
self.assertIn('(' + name + ')', text)
self.assertIn('modified, unofficial SDL sources', text)
self.assertIn('Permission is hereby granted', (ROOT / 'LICENSES/MIT-trevlars.txt').read_text())
self.assertIn('This notice may not be removed', (ROOT / 'LICENSES/SDL-zlib.txt').read_text())


if __name__ == '__main__':
unittest.main()