Merge branch 'voice'
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "source/extern/opus"]
|
||||
path = source/extern/opus
|
||||
url = https://github.com/xiph/opus
|
@ -1,7 +1,8 @@
|
||||
{
|
||||
"opensb": {
|
||||
"groups": {
|
||||
"camera": { "name": "Camera" }
|
||||
"camera": { "name": "Camera" },
|
||||
"voice": { "name": "Voice" }
|
||||
},
|
||||
"name": "Open^#ebd74a;Starbound",
|
||||
"binds": {
|
||||
@ -21,13 +22,10 @@
|
||||
"group" : "camera",
|
||||
"name": "Zoom Out"
|
||||
},
|
||||
"test": {
|
||||
"default": [{
|
||||
"type": "key",
|
||||
"value": "C",
|
||||
"mods": ["LShift"]
|
||||
}],
|
||||
"name": "Test Bind"
|
||||
"pushToTalk": {
|
||||
"default": [],
|
||||
"group" : "voice",
|
||||
"name": "Push To Talk"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
{
|
||||
"universeScriptContexts" : { "OpenStarbound" : ["/scripts/opensb/universeclient/universeclient.lua"] },
|
||||
|
||||
// Disables scissoring and letterboxing on vanilla and modded warp cinematics
|
||||
"warpCinematicBase" : {
|
||||
"scissor" : false,
|
||||
|
BIN
assets/opensb/interface/opensb/voicechat/activity.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
assets/opensb/interface/opensb/voicechat/activityback.png
Normal file
After Width: | Height: | Size: 326 B |
BIN
assets/opensb/interface/opensb/voicechat/bigbutton.png
Normal file
After Width: | Height: | Size: 223 B |
BIN
assets/opensb/interface/opensb/voicechat/bigbuttonback.png
Normal file
After Width: | Height: | Size: 496 B |
BIN
assets/opensb/interface/opensb/voicechat/body.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/opensb/interface/opensb/voicechat/device.png
Normal file
After Width: | Height: | Size: 195 B |
BIN
assets/opensb/interface/opensb/voicechat/deviceback.png
Normal file
After Width: | Height: | Size: 447 B |
BIN
assets/opensb/interface/opensb/voicechat/footer.png
Normal file
After Width: | Height: | Size: 250 B |
BIN
assets/opensb/interface/opensb/voicechat/header.png
Normal file
After Width: | Height: | Size: 569 B |
BIN
assets/opensb/interface/opensb/voicechat/indicator/back.png
Normal file
After Width: | Height: | Size: 611 B |
BIN
assets/opensb/interface/opensb/voicechat/indicator/front.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/opensb/interface/opensb/voicechat/pushtotalk.png
Normal file
After Width: | Height: | Size: 207 B |
BIN
assets/opensb/interface/opensb/voicechat/pushtotalkback.png
Normal file
After Width: | Height: | Size: 378 B |
174
assets/opensb/interface/opensb/voicechat/voicechat.config
Normal file
@ -0,0 +1,174 @@
|
||||
{
|
||||
"scripts" : ["/interface/opensb/voicechat/voicechat.lua"],
|
||||
"scriptDelta" : 1,
|
||||
"scriptWidgetCallbacks" : [
|
||||
"selectDevice",
|
||||
"voiceToggle",
|
||||
"switchVoiceMode"
|
||||
],
|
||||
|
||||
"canvasClickCallbacks" : {
|
||||
"inputVolume" : "inputVolume",
|
||||
"voiceVolume" : "voiceVolume",
|
||||
"threshold" : "threshold"
|
||||
},
|
||||
|
||||
"gui" : {
|
||||
"panefeature" : {
|
||||
"type" : "panefeature",
|
||||
"positionLocked" : false
|
||||
},
|
||||
"background" : {
|
||||
"type" : "background",
|
||||
"fileHeader" : "/interface/opensb/voicechat/header.png",
|
||||
"fileBody" : "/interface/opensb/voicechat/body.png",
|
||||
"fileFooter" : "/interface/opensb/voicechat/footer.png"
|
||||
},
|
||||
|
||||
"voiceVolumeLabel" : {
|
||||
"type" : "label",
|
||||
"value" : "THEIR VOLUME",
|
||||
"position" : [26, 178],
|
||||
"wrapWidth" : 48,
|
||||
"lineSpacing" : 0.75,
|
||||
"hAnchor" : "mid",
|
||||
"vAnchor" : "mid"
|
||||
},
|
||||
"voiceVolume" : {
|
||||
"type" : "canvas",
|
||||
"rect" : [50, 171, 247, 186],
|
||||
"captureMouseEvents" : true,
|
||||
"captureKeyboardEvents" : false
|
||||
},
|
||||
|
||||
"inputVolumeLabel" : {
|
||||
"type" : "label",
|
||||
"value" : "YOUR VOLUME",
|
||||
"position" : [26, 158],
|
||||
"wrapWidth" : 48,
|
||||
"lineSpacing" : 0.75,
|
||||
"hAnchor" : "mid",
|
||||
"vAnchor" : "mid"
|
||||
},
|
||||
"inputVolume" : {
|
||||
"type" : "canvas",
|
||||
"rect" : [50, 151, 247, 166],
|
||||
"captureMouseEvents" : true,
|
||||
"captureKeyboardEvents" : false
|
||||
},
|
||||
|
||||
"enableVoiceToggleBack" : {
|
||||
"type" : "image",
|
||||
"file" : "/interface/opensb/voicechat/bigbuttonback.png?multiply=0f0",
|
||||
"position" : [2, 189],
|
||||
"zlevel" : -1
|
||||
},
|
||||
"enableVoiceToggle" : {
|
||||
"type" : "button",
|
||||
"pressedOffset" : [0, 0],
|
||||
"position" : [2, 189],
|
||||
"base" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=fff0;000=0007",
|
||||
"hover" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=fff7;000=3337",
|
||||
"press" : "/interface/opensb/voicechat/bigbutton.png?replace;fff=000;000=7777",
|
||||
"callback" : "voiceToggle",
|
||||
"fontSize" : 16,
|
||||
"zlevel" : 1
|
||||
},
|
||||
|
||||
|
||||
"voiceModeLabel" : {
|
||||
"type" : "label",
|
||||
"value" : "VOICE MODE",
|
||||
"position" : [26, 133],
|
||||
"wrapWidth" : 32,
|
||||
"lineSpacing" : 0.75,
|
||||
"hAnchor" : "mid",
|
||||
"vAnchor" : "mid"
|
||||
},
|
||||
"pushToTalkBack" : {
|
||||
"type" : "image",
|
||||
"file" : "/interface/opensb/voicechat/pushtotalkback.png?multiply=0f0",
|
||||
"position" : [50, 121],
|
||||
"zlevel" : -1
|
||||
},
|
||||
"pushToTalk" : {
|
||||
"type" : "button",
|
||||
"pressedOffset" : [0, 0],
|
||||
"position" : [50, 121],
|
||||
"base" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=fff0;000=0007",
|
||||
"hover" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=fff7;000=3337",
|
||||
"press" : "/interface/opensb/voicechat/pushtotalk.png?replace;fff=000;000=7777",
|
||||
"callback" : "switchVoiceMode",
|
||||
"fontSize" : 16,
|
||||
"zlevel" : 1
|
||||
},
|
||||
|
||||
"voiceActivityBack" : {
|
||||
"type" : "image",
|
||||
"file" : "/interface/opensb/voicechat/activityback.png?multiply=0f0",
|
||||
"position" : [167, 121],
|
||||
"zlevel" : -1
|
||||
},
|
||||
"voiceActivity" : {
|
||||
"type" : "button",
|
||||
"pressedOffset" : [0, 0],
|
||||
"position" : [167, 121],
|
||||
"base" : "/interface/opensb/voicechat/activity.png?replace;fff=fff0;000=0007",
|
||||
"hover" : "/interface/opensb/voicechat/activity.png?replace;fff=fff7;000=3337",
|
||||
"press" : "/interface/opensb/voicechat/activity.png?replace;fff=000;000=7777",
|
||||
"callback" : "switchVoiceMode",
|
||||
"fontSize" : 16,
|
||||
"zlevel" : 1
|
||||
},
|
||||
|
||||
"thresholdLevel" : {
|
||||
"type" : "label",
|
||||
"value" : "THRESHOLD",
|
||||
"position" : [26, 109],
|
||||
"wrapWidth" : 48,
|
||||
"lineSpacing" : 0.75,
|
||||
"hAnchor" : "mid",
|
||||
"vAnchor" : "mid"
|
||||
},
|
||||
"threshold" : {
|
||||
"type" : "canvas",
|
||||
"rect" : [50, 102, 247, 117],
|
||||
"captureMouseEvents" : true,
|
||||
"captureKeyboardEvents" : false
|
||||
},
|
||||
|
||||
"devices" : {
|
||||
"type" : "scrollArea",
|
||||
"rect" : [3, 16, 248, 98],
|
||||
"children" : {
|
||||
"list" : {
|
||||
"type" : "list",
|
||||
"schema" : {
|
||||
"selectedBG" : "/interface/opensb/voicechat/deviceback.png?multiply=0f0",
|
||||
"unselectedBG" : "/interface/opensb/voicechat/deviceback.png?multiply=222",
|
||||
"spacing" : [0, 1],
|
||||
"memberSize" : [234, 16],
|
||||
"listTemplate" : {
|
||||
"background" : {
|
||||
"type" : "image",
|
||||
"file" : "/interface/opensb/voicechat/deviceback.png?multiply=222",
|
||||
"position" : [0, 0],
|
||||
"zlevel" : -1
|
||||
},
|
||||
"button" : {
|
||||
"type" : "button",
|
||||
"callback" : "selectDevice",
|
||||
"caption" : "Unnamed",
|
||||
"base" : "/interface/opensb/voicechat/device.png?replace;fff=fff0;000=0007",
|
||||
"hover" : "/interface/opensb/voicechat/device.png?replace;fff=fff7;000=3337",
|
||||
"press" : "/interface/opensb/voicechat/device.png?replace;fff=000;000=7777",
|
||||
"pressedOffset" : [0, 0],
|
||||
"position" : [0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
228
assets/opensb/interface/opensb/voicechat/voicechat.lua
Normal file
@ -0,0 +1,228 @@
|
||||
--constants
|
||||
local PATH = "/interface/opensb/voicechat/"
|
||||
local DEVICE_LIST_WIDGET = "devices.list"
|
||||
local DEFAULT_DEVICE_NAME = "Use System Default"
|
||||
local NULL_DEVICE_NAME = "No Audio Device"
|
||||
local COLD_COLOR = {25, 255, 255, 225}
|
||||
local HOT_COLOR = {255, 96, 96, 225}
|
||||
local MINIMUM_DB = -80
|
||||
local VOICE_MAX, INPUT_MAX = 1.75, 1.0
|
||||
local MID = 7.5
|
||||
|
||||
local fmt = string.format
|
||||
|
||||
local debugging = false
|
||||
local function log(...)
|
||||
if not debugging then return end
|
||||
sb.logInfo(...)
|
||||
end
|
||||
|
||||
local function mapToRange(x, min, max)
|
||||
return math.min(1, math.max(0, (x - min)) / max)
|
||||
end
|
||||
|
||||
local function linear(a, b, c)
|
||||
return a + (b - a) * c
|
||||
end
|
||||
|
||||
local settings = {}
|
||||
|
||||
local function set(k, v)
|
||||
settings[k] = v
|
||||
local newSettings = jobject()
|
||||
newSettings[k] = v
|
||||
voice.mergeSettings(newSettings)
|
||||
return v
|
||||
end
|
||||
|
||||
local devicesToWidgets = {}
|
||||
local widgetsToDevices = {}
|
||||
local nullWidget
|
||||
local function addDeviceToList(deviceName)
|
||||
local name = widget.addListItem(DEVICE_LIST_WIDGET)
|
||||
widget.setText(fmt("%s.%s.button", DEVICE_LIST_WIDGET, name), deviceName)
|
||||
widgetsToDevices[name] = deviceName
|
||||
devicesToWidgets[deviceName] = name
|
||||
log("Added audio device '%s' to list", deviceName)
|
||||
return name
|
||||
end
|
||||
|
||||
function selectDevice()
|
||||
local selected = widget.getListSelected(DEVICE_LIST_WIDGET)
|
||||
if selected == nullWidget then
|
||||
set("inputEnabled", false)
|
||||
widget.setListSelected(DEVICE_LIST_WIDGET, nullWidget)
|
||||
end
|
||||
local deviceName = widgetsToDevices[selected]
|
||||
if deviceName == DEFAULT_DEVICE_NAME then deviceName = nil end
|
||||
|
||||
if settings.deviceName == deviceName then
|
||||
local inputEnabled = set("inputEnabled", not settings.inputEnabled)
|
||||
widget.setListSelected(DEVICE_LIST_WIDGET, inputEnabled and selected or nullWidget)
|
||||
else
|
||||
set("deviceName", deviceName)
|
||||
set("inputEnabled", true)
|
||||
end
|
||||
end
|
||||
|
||||
local function initCallbacks()
|
||||
widget.registerMemberCallback(DEVICE_LIST_WIDGET, "selectDevice", selectDevice)
|
||||
end
|
||||
|
||||
local function updateVoiceButton()
|
||||
local enabled = settings.enabled
|
||||
widget.setText("enableVoiceToggle", enabled and "^#0f0;disable voice chat" or "^#f00;enable voice chat")
|
||||
widget.setImage("enableVoiceToggleBack", PATH .. "bigbuttonback.png?multiply=" .. (enabled and "0f0" or "f00"))
|
||||
end
|
||||
|
||||
local function updateVoiceModeButtons()
|
||||
local pushToTalk = settings.inputMode:lower() == "pushtotalk"
|
||||
widget.setImage("pushToTalkBack", PATH .. "pushtotalkback.png?multiply=" .. (pushToTalk and "0f0" or "f00"))
|
||||
widget.setImage("voiceActivityBack", PATH .. "activityback.png?multiply=" .. (pushToTalk and "f00" or "0f0"))
|
||||
widget.setText("pushToTalk", pushToTalk and "^#0f0;PUSH TO TALK" or "^#f00;PUSH TO TALK")
|
||||
widget.setText("voiceActivity", pushToTalk and "^#f00;ACTIVITY" or "^#0f0;ACTIVITY")
|
||||
end
|
||||
|
||||
local voiceCanvas, inputCanvas = nil, nil
|
||||
local function updateVolumeCanvas(canvas, volume, multiplier)
|
||||
canvas:clear()
|
||||
local lineEnd = 1 + volume * 195
|
||||
local lineColor = {95, 110, 255, 225}
|
||||
local multiplied = volume * multiplier
|
||||
if multiplied > 1 then
|
||||
local level = (multiplied - 1) / (multiplier - 1)
|
||||
for i = 1, 4 do
|
||||
lineColor[i] = linear(lineColor[i], HOT_COLOR[i], level)
|
||||
end
|
||||
else
|
||||
for i = 1, 4 do
|
||||
lineColor[i] = linear(lineColor[i], COLD_COLOR[i], 1 - multiplied)
|
||||
end
|
||||
end
|
||||
|
||||
canvas:drawLine({1, MID}, {lineEnd, MID}, lineColor, 60)
|
||||
canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60)
|
||||
local str = volume == 0 and "^#f00,shadow;MUTED" or fmt("^shadow;%s%%", math.floor(volume * multiplier * 1000) * 0.1)
|
||||
canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255})
|
||||
end
|
||||
|
||||
local thresholdCanvas = nil
|
||||
local function updateThresholdCanvas(canvas, dB)
|
||||
canvas:clear()
|
||||
local scale = pane.scale()
|
||||
local lineEnd = 1 + (1 - (dB / MINIMUM_DB)) * 195
|
||||
local lineColor = {255, 255, 0, 127}
|
||||
canvas:drawLine({1, 2}, {lineEnd, 2}, lineColor, scale)
|
||||
canvas:drawLine({1, 13}, {lineEnd, 13}, lineColor, scale)
|
||||
lineColor[4] = 64
|
||||
canvas:drawLine({lineEnd, 2}, {196, 2}, lineColor, scale)
|
||||
canvas:drawLine({lineEnd, 13}, {196, 13}, lineColor, scale)
|
||||
canvas:drawLine({lineEnd - 0.5, MID}, {lineEnd + 0.5, MID}, {255, 255, 255, 200}, 60)
|
||||
|
||||
local loudness = 1 - (voice.speaker().smoothDecibels / MINIMUM_DB)
|
||||
local loudnessEnd = math.min(1 + loudness * 195, 196)
|
||||
if loudnessEnd > 0 then
|
||||
lineColor[4] = 200
|
||||
canvas:drawLine({1, MID}, {loudnessEnd, MID}, lineColor, 4 * scale)
|
||||
end
|
||||
|
||||
local str = fmt("^shadow;%sdB", math.floor(dB * 10) * 0.1)
|
||||
canvas:drawText(str, {position = {92.5, MID}, horizontalAnchor = "mid", verticalAnchor = "mid"}, 16, {255, 255, 255, 255})
|
||||
end
|
||||
|
||||
function init()
|
||||
settings = voice.getSettings()
|
||||
voiceCanvas = widget.bindCanvas("voiceVolume")
|
||||
inputCanvas = widget.bindCanvas("inputVolume")
|
||||
thresholdCanvas = widget.bindCanvas("threshold")
|
||||
end
|
||||
|
||||
function displayed()
|
||||
devicesToWidgets = {}
|
||||
widgetsToDevices = {}
|
||||
widget.clearListItems(DEVICE_LIST_WIDGET)
|
||||
initCallbacks()
|
||||
|
||||
addDeviceToList(DEFAULT_DEVICE_NAME)
|
||||
|
||||
for i, v in pairs(voice.devices()) do
|
||||
addDeviceToList(v)
|
||||
end
|
||||
|
||||
nullWidget = widget.addListItem(DEVICE_LIST_WIDGET)
|
||||
local nullWidgetPath = fmt("%s.%s", DEVICE_LIST_WIDGET, nullWidget)
|
||||
widget.setPosition(nullWidgetPath, {0, 10000})
|
||||
widget.setVisible(nullWidgetPath, false)
|
||||
|
||||
local preferredDeviceWidget = devicesToWidgets[settings.deviceName or DEFAULT_DEVICE_NAME]
|
||||
if preferredDeviceWidget and settings.inputEnabled then
|
||||
widget.setListSelected(DEVICE_LIST_WIDGET, preferredDeviceWidget)
|
||||
end
|
||||
|
||||
updateVoiceButton()
|
||||
updateVoiceModeButtons()
|
||||
updateVolumeCanvas(voiceCanvas, settings.outputVolume / VOICE_MAX, VOICE_MAX)
|
||||
updateVolumeCanvas(inputCanvas, settings.inputVolume / INPUT_MAX, INPUT_MAX)
|
||||
updateThresholdCanvas(thresholdCanvas, settings.threshold)
|
||||
end
|
||||
|
||||
function update()
|
||||
updateThresholdCanvas(thresholdCanvas, settings.threshold)
|
||||
end
|
||||
|
||||
local function sliderToValue(x)
|
||||
return mapToRange(x, 5, 187)
|
||||
end
|
||||
|
||||
local function mouseInSlider(mouse)
|
||||
return mouse[1] > 0 and mouse[1] < 197
|
||||
and mouse[2] > 0 and mouse[2] < 16
|
||||
end
|
||||
|
||||
local function handleVolume(canvas, mouse, multiplier, setter)
|
||||
if not mouseInSlider(mouse) then return end
|
||||
|
||||
local volumePreMul = sliderToValue(mouse[1])
|
||||
local volume = volumePreMul * multiplier;
|
||||
if math.abs(volume - 1) < 0.01 then
|
||||
volumePreMul = 1 / multiplier
|
||||
volume = 1
|
||||
end
|
||||
|
||||
updateVolumeCanvas(canvas, volumePreMul, multiplier)
|
||||
setter(volume)
|
||||
end
|
||||
|
||||
function voiceVolume(mouse, button)
|
||||
if button ~= 0 then return end
|
||||
handleVolume(voiceCanvas, mouse, VOICE_MAX, function(v) set("outputVolume", v) end)
|
||||
end
|
||||
|
||||
function inputVolume(mouse, button)
|
||||
if button ~= 0 then return end
|
||||
handleVolume(inputCanvas, mouse, INPUT_MAX, function(v) set("inputVolume", v) end)
|
||||
end
|
||||
|
||||
function threshold(mouse, button)
|
||||
if button ~= 0 then return end
|
||||
if not mouseInSlider(mouse) then return end
|
||||
local dB = (1 - sliderToValue(mouse[1])) * MINIMUM_DB
|
||||
set("threshold", dB)
|
||||
|
||||
updateThresholdCanvas(thresholdCanvas, dB)
|
||||
end
|
||||
|
||||
function switchVoiceMode(mode)
|
||||
log("switching voice mode to %s", tostring(mode))
|
||||
local success, err = pcall(function()
|
||||
set("inputMode", mode)
|
||||
updateVoiceModeButtons()
|
||||
end)
|
||||
if not success then log("%s", err) end
|
||||
end
|
||||
|
||||
function voiceToggle()
|
||||
set("enabled", not settings.enabled)
|
||||
widget.playSound(fmt("/sfx/interface/voice_%s.ogg", settings.enabled and "on" or "off"), 0)
|
||||
updateVoiceButton()
|
||||
end
|
BIN
assets/opensb/interface/optionsmenu/body_blank.png
Normal file
After Width: | Height: | Size: 1017 B |
BIN
assets/opensb/interface/optionsmenu/duocontrolsbutton.png
Normal file
After Width: | Height: | Size: 159 B |
BIN
assets/opensb/interface/optionsmenu/duocontrolsbuttonhover.png
Normal file
After Width: | Height: | Size: 154 B |
@ -1,25 +1,48 @@
|
||||
{
|
||||
"paneLayout" : {
|
||||
"voiceLabel" : {
|
||||
"type" : "label",
|
||||
"position" : [119, 185],
|
||||
"hAnchor" : "mid",
|
||||
"value" : "VOICE"
|
||||
},
|
||||
"showVoiceSettings" : {
|
||||
"type" : "button",
|
||||
"position" : [30, 169],
|
||||
"caption" : "Settings",
|
||||
"base" : "/interface/optionsmenu/duocontrolsbutton.png",
|
||||
"hover" : "/interface/optionsmenu/duocontrolsbuttonhover.png"
|
||||
},
|
||||
"showVoicePlayers" : {
|
||||
"type" : "button",
|
||||
"disabled" : true,
|
||||
"position" : [133, 169],
|
||||
"caption" : "^#a0a000,font=iosevka-semiboldoblique;TODO^#aa7;:^reset; Players",
|
||||
"base" : "/interface/optionsmenu/duocontrolsbutton.png",
|
||||
"hover" : "/interface/optionsmenu/duocontrolsbuttonhover.png"
|
||||
},
|
||||
"showKeybindings" : {
|
||||
"type" : "button",
|
||||
"position" : [150, 95],
|
||||
"position" : [153, 95],
|
||||
"caption" : "Game Binds",
|
||||
"base" : "/interface/optionsmenu/controlsbutton.png",
|
||||
"hover" : "/interface/optionsmenu/controlsbuttonhover.png"
|
||||
"base" : "/interface/optionsmenu/tricontrolsbutton.png",
|
||||
"hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
|
||||
},
|
||||
"showModBindings" : {
|
||||
"type" : "button",
|
||||
"position" : [87, 95],
|
||||
"caption" : "Mod Binds",
|
||||
"base" : "/interface/optionsmenu/controlsbutton.png",
|
||||
"hover" : "/interface/optionsmenu/controlsbuttonhover.png"
|
||||
"base" : "/interface/optionsmenu/tricontrolsbutton.png",
|
||||
"hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
|
||||
},
|
||||
"showGraphics" : {
|
||||
"type" : "button",
|
||||
"position" : [24, 95],
|
||||
"position" : [21, 95],
|
||||
"caption" : "Graphics",
|
||||
"base" : "/interface/optionsmenu/controlsbutton.png",
|
||||
"hover" : "/interface/optionsmenu/controlsbuttonhover.png"
|
||||
}
|
||||
"base" : "/interface/optionsmenu/tricontrolsbutton.png",
|
||||
"hover" : "/interface/optionsmenu/tricontrolsbuttonhover.png"
|
||||
},
|
||||
"sfxValueLabel" : { "position" : [192, 142] }, // this is 2px too low in vanilla lol
|
||||
"musicSlider" : { "position" : [62, 126] }
|
||||
}
|
||||
}
|
BIN
assets/opensb/interface/optionsmenu/shine.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
Before Width: | Height: | Size: 213 B After Width: | Height: | Size: 213 B |
@ -1,4 +1,5 @@
|
||||
{
|
||||
"genericScriptContexts" : { "OpenStarbound" : "/scripts/opensb/player/player.lua" },
|
||||
"wireConfig" : {
|
||||
"innerBrightnessScale" : 20,
|
||||
"firstStripeThickness" : 0.6,
|
||||
|
30
assets/opensb/scripts/opensb/player/commands.lua
Normal file
@ -0,0 +1,30 @@
|
||||
local module = {}
|
||||
modules.commands = module
|
||||
|
||||
local commands = {}
|
||||
local function command(name, func)
|
||||
commands[name] = func
|
||||
end
|
||||
|
||||
function module.init()
|
||||
for name, func in pairs(commands) do
|
||||
message.setHandler("/" .. name, function(isLocal, _, ...)
|
||||
if not isLocal then
|
||||
return
|
||||
else
|
||||
return func(...)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
command("run", function(src)
|
||||
local success, result = pcall(loadstring, src, "/run")
|
||||
if not success then
|
||||
return "^#f00;compile error: " .. result
|
||||
else
|
||||
local success, result = pcall(result)
|
||||
return not success and "^#f00;error: " .. result or sb.printJson(result)
|
||||
end
|
||||
end)
|
2
assets/opensb/scripts/opensb/player/player.lua
Normal file
@ -0,0 +1,2 @@
|
||||
require "/scripts/opensb/util/modules.lua"
|
||||
modules("/scripts/opensb/player/", {"commands"})
|
@ -0,0 +1,2 @@
|
||||
require "/scripts/opensb/util/modules.lua"
|
||||
modules("/scripts/opensb/universeclient/", {"voicemanager"})
|
168
assets/opensb/scripts/opensb/universeclient/voicemanager.lua
Normal file
@ -0,0 +1,168 @@
|
||||
-- Manages the voice HUD indicators and click-to-mute/unmute.
|
||||
|
||||
local fmt = string.format
|
||||
local sqrt, min, max = math.sqrt, math.min, math.max
|
||||
|
||||
local module = {}
|
||||
modules.voice_manager = module
|
||||
|
||||
--constants
|
||||
local INDICATOR_PATH = "/interface/opensb/voicechat/indicator/"
|
||||
local BACK_INDICATOR_IMAGE = INDICATOR_PATH .. "back.png"
|
||||
local FRONT_INDICATOR_IMAGE = INDICATOR_PATH .. "front.png"
|
||||
local FRONT_MUTED_INDICATOR_IMAGE = INDICATOR_PATH .. "front_muted.png"
|
||||
local INDICATOR_SIZE = {300, 48}
|
||||
local LINE_PADDING = 12
|
||||
local LINE_WIDTH = 296
|
||||
local LINE_WIDTH_PADDED = LINE_WIDTH - LINE_PADDING
|
||||
local LINE_COLOR = {50, 210, 255, 255}
|
||||
local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330"
|
||||
local NAME_PREFIX = "^noshadow,white,set;"
|
||||
|
||||
local function dbToLoudness(db) return 2 ^ (db / 8) end
|
||||
|
||||
local canvas
|
||||
|
||||
local linePaddingDrawable
|
||||
do
|
||||
local drawable = { image = BACK_INDICATOR_IMAGE, position = {0, 0}, color = LINE_COLOR, centered = false }
|
||||
function linePaddingDrawable(a, b)
|
||||
drawable.image = BACK_INDICATOR_IMAGE .. fmt("?crop=%i;%i;%i;%i?fade=fff;1", a, 0, b, INDICATOR_SIZE[2])
|
||||
drawable.position[1] = a
|
||||
return drawable;
|
||||
end
|
||||
end
|
||||
|
||||
local lineDrawable = { line = {{LINE_PADDING, 24}, {10, 24}}, width = 48, color = LINE_COLOR }
|
||||
local function line(pos, value)
|
||||
local width = math.floor((LINE_WIDTH * value) + 0.5)
|
||||
LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350))
|
||||
if width > 0 then
|
||||
canvas:drawDrawable(linePaddingDrawable(0, math.min(12, width)), pos)
|
||||
if width > 12 then
|
||||
lineDrawable.line[2][1] = math.min(width, LINE_WIDTH_PADDED)
|
||||
canvas:drawDrawable(lineDrawable, pos)
|
||||
if width > LINE_WIDTH_PADDED then
|
||||
canvas:drawDrawable(linePaddingDrawable(LINE_WIDTH_PADDED, width), pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local drawable = { image = BACK_INDICATOR_IMAGE, centered = false }
|
||||
local textPositioning = { position = {0, 0}, horizontalAnchor = "left", verticalAnchor = "mid" }
|
||||
|
||||
local hoveredSpeaker = nil
|
||||
local hoveredSpeakerIndex = 1
|
||||
local hoveredSpeakerPosition = {0, 0}
|
||||
local function mouseOverSpeaker(mouse, pos, expand)
|
||||
expand = tonumber(expand) or 0
|
||||
return (mouse[1] > pos[1] - expand and mouse[1] < pos[1] + 300 + expand)
|
||||
and (mouse[2] > pos[2] - expand and mouse[2] < pos[2] + 48 + expand)
|
||||
end
|
||||
|
||||
local function drawSpeakerBar(mouse, pos, speaker, i)
|
||||
drawable.image = BACK_INDICATOR_IMAGE
|
||||
canvas:drawDrawable(drawable, pos)
|
||||
line(pos, dbToLoudness(speaker.smoothDecibels))
|
||||
local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos)
|
||||
|
||||
textPositioning.position = {pos[1] + 49, pos[2] + 24}
|
||||
textPositioning.horizontalAnchor = "left"
|
||||
local text = NAME_PREFIX ..
|
||||
(hovering and (speaker.muted and "^#31d2f7;Unmute^reset; " or "^#f43030;Mute^reset; ") or "")
|
||||
.. speaker.name
|
||||
canvas:drawText(text, textPositioning, 16, nil, nil, nil, FONT_DIRECTIVES)
|
||||
drawable.image = speaker.muted and FRONT_MUTED_INDICATOR_IMAGE or FRONT_INDICATOR_IMAGE
|
||||
canvas:drawDrawable(drawable, pos)
|
||||
|
||||
if hovering then
|
||||
hoveredSpeaker = speaker
|
||||
hoveredSpeakerIndex = i
|
||||
hoveredSpeakerPosition = pos
|
||||
if input.keyHeld("LShift") then
|
||||
textPositioning.position = {pos[1] + 288, pos[2] + 24}
|
||||
textPositioning.horizontalAnchor = "right"
|
||||
canvas:drawText("^#fff7,font=iosevka-semibold;" .. tostring(speaker.speakerId), textPositioning, 16, nil, nil, nil, FONT_DIRECTIVES)
|
||||
end
|
||||
|
||||
if input.mouseDown("MouseLeft") then
|
||||
local muted = not voice.speakerMuted(speaker.speakerId)
|
||||
interface.queueMessage((muted and "^#f43030;Muted^reset; " or "^#31d2f7;Unmuted^reset; ") .. speaker.name, 4, 0.5)
|
||||
voice.setSpeakerMuted(speaker.speakerId, muted)
|
||||
speaker.muted = muted
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function simulateSpeakers()
|
||||
local speakers = {}
|
||||
for i = 2, 5 + math.floor((math.sin(os.clock()) * 4) + .5) do
|
||||
local dB = -48 + 48 * math.sin(os.clock() + (i * 0.5))
|
||||
speakers[i] = {
|
||||
speakerId = i,
|
||||
entityId = -65536 * i,
|
||||
name = "Player " .. i,
|
||||
decibels = dB,
|
||||
smoothDecibels = dB,
|
||||
muted = false
|
||||
}
|
||||
end
|
||||
return speakers
|
||||
end
|
||||
|
||||
local function drawIndicators()
|
||||
canvas:clear()
|
||||
local screenSize = canvas:size()
|
||||
local mousePosition = canvas:mousePosition()
|
||||
local basePos = {screenSize[1] - 350, 50}
|
||||
|
||||
local speakersRemaining, speakers = {}, {}
|
||||
local hoveredSpeakerId = nil
|
||||
if hoveredSpeaker then
|
||||
if not mouseOverSpeaker(mousePosition, hoveredSpeakerPosition, 16) then
|
||||
hoveredSpeaker = nil
|
||||
else
|
||||
hoveredSpeakerId = hoveredSpeaker.speakerId
|
||||
end
|
||||
end
|
||||
|
||||
local speakerCount = 0
|
||||
for i, speaker in pairs(voice.speakers()) do
|
||||
local speakerId = speaker.speakerId
|
||||
speakersRemaining[speakerId] = true
|
||||
if speakerId == hoveredSpeakerId then
|
||||
hoveredSpeaker = speaker
|
||||
else
|
||||
speakerCount = speakerCount + 1
|
||||
speakers[speakerCount] = speaker
|
||||
end
|
||||
end
|
||||
|
||||
if hoveredSpeaker then
|
||||
local len = #speakers
|
||||
if hoveredSpeakerIndex > len then
|
||||
for i = len + 1, hoveredSpeakerIndex - 1 do
|
||||
speakers[i] = false
|
||||
end
|
||||
speakers[hoveredSpeakerIndex] = hoveredSpeaker
|
||||
else
|
||||
table.insert(speakers, hoveredSpeakerIndex, hoveredSpeaker)
|
||||
end
|
||||
end
|
||||
|
||||
for i, v in pairs(speakers) do
|
||||
if v then
|
||||
local pos = {basePos[1], basePos[2] + (i - 1) * 52}
|
||||
drawSpeakerBar(mousePosition, pos, v, i)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function module.init()
|
||||
canvas = interface.bindCanvas("voice", true)
|
||||
end
|
||||
|
||||
function module.update()
|
||||
drawIndicators()
|
||||
end
|
31
assets/opensb/scripts/opensb/util/modules.lua
Normal file
@ -0,0 +1,31 @@
|
||||
modules = setmetatable({}, {__call = function(this, path, names)
|
||||
for i, name in pairs(names) do
|
||||
require(path .. name .. ".lua")
|
||||
end
|
||||
end})
|
||||
|
||||
local modules, type = modules, type
|
||||
local function call(func, ...)
|
||||
if type(func) == "function" then
|
||||
return func(...)
|
||||
end
|
||||
end
|
||||
|
||||
function init(...)
|
||||
script.setUpdateDelta(1)
|
||||
for i, module in pairs(modules) do
|
||||
call(module.init, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function update(...)
|
||||
for i, module in pairs(modules) do
|
||||
call(module.update, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function uninit(...)
|
||||
for i, module in pairs(modules) do
|
||||
call(module.uninit, ...)
|
||||
end
|
||||
end
|
BIN
assets/opensb/sfx/interface/voice_off.ogg
Normal file
BIN
assets/opensb/sfx/interface/voice_on.ogg
Normal file
@ -437,6 +437,7 @@ SET (STAR_EXT_LIBS ${STAR_EXT_LIBS}
|
||||
${FREETYPE_LIBRARY}
|
||||
${PNG_LIBRARY}
|
||||
${ZLIB_LIBRARY}
|
||||
opus
|
||||
)
|
||||
|
||||
IF (STAR_BUILD_GUI)
|
||||
|
@ -44,9 +44,16 @@ public:
|
||||
virtual bool setCursorImage(const String& id, const ImageConstPtr& image, unsigned scale, const Vec2I& offset) = 0;
|
||||
virtual void setAcceptingTextInput(bool acceptingTextInput) = 0;
|
||||
|
||||
|
||||
|
||||
virtual AudioFormat enableAudio() = 0;
|
||||
virtual void disableAudio() = 0;
|
||||
|
||||
typedef void (__cdecl* AudioCallback)(void* userdata, uint8_t* stream, int len);
|
||||
|
||||
virtual bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, AudioCallback callback) = 0;
|
||||
virtual bool closeAudioInputDevice() = 0;
|
||||
|
||||
virtual void setClipboard(String text) = 0;
|
||||
virtual Maybe<String> getClipboard() = 0;
|
||||
|
||||
|
@ -248,9 +248,15 @@ public:
|
||||
if (SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER))
|
||||
throw ApplicationException(strf("Couldn't initialize SDL Controller: {}", SDL_GetError()));
|
||||
|
||||
Logger::info("Application: Initializing SDL Sound");
|
||||
#ifdef STAR_SYSTEM_WINDOWS // Newer SDL is defaulting to xaudio2, which does not support audio capture
|
||||
SDL_setenv("SDL_AUDIODRIVER", "directsound", 1);
|
||||
#endif
|
||||
|
||||
Logger::info("Application: Initializing SDL Audio");
|
||||
if (SDL_InitSubSystem(SDL_INIT_AUDIO))
|
||||
throw ApplicationException(strf("Couldn't initialize SDL Sound: {}", SDL_GetError()));
|
||||
throw ApplicationException(strf("Couldn't initialize SDL Audio: {}", SDL_GetError()));
|
||||
|
||||
Logger::info("Application: using Audio Driver '{}'", SDL_GetCurrentAudioDriver());
|
||||
|
||||
SDL_JoystickEventState(SDL_ENABLE);
|
||||
|
||||
@ -292,15 +298,15 @@ public:
|
||||
};
|
||||
|
||||
SDL_AudioSpec obtained = {};
|
||||
m_sdlAudioDevice = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
|
||||
if (!m_sdlAudioDevice) {
|
||||
m_sdlAudioOutputDevice = SDL_OpenAudioDevice(NULL, 0, &desired, &obtained, 0);
|
||||
if (!m_sdlAudioOutputDevice) {
|
||||
Logger::error("Application: Could not open audio device, no sound available!");
|
||||
} else if (obtained.freq != desired.freq || obtained.channels != desired.channels || obtained.format != desired.format) {
|
||||
SDL_CloseAudioDevice(m_sdlAudioDevice);
|
||||
SDL_CloseAudioDevice(m_sdlAudioOutputDevice);
|
||||
Logger::error("Application: Could not open 44.1khz / 16 bit stereo audio device, no sound available!");
|
||||
} else {
|
||||
Logger::info("Application: Opened default audio device with 44.1khz / 16 bit stereo audio, {} sample size buffer", obtained.samples);
|
||||
SDL_PauseAudioDevice(m_sdlAudioDevice, 0);
|
||||
SDL_PauseAudioDevice(m_sdlAudioOutputDevice, 0);
|
||||
}
|
||||
|
||||
m_renderer = make_shared<OpenGl20Renderer>();
|
||||
@ -311,7 +317,10 @@ public:
|
||||
|
||||
~SdlPlatform() {
|
||||
|
||||
SDL_CloseAudioDevice(m_sdlAudioDevice);
|
||||
if (m_sdlAudioOutputDevice)
|
||||
SDL_CloseAudioDevice(m_sdlAudioOutputDevice);
|
||||
|
||||
closeAudioInputDevice();
|
||||
|
||||
m_renderer.reset();
|
||||
|
||||
@ -321,6 +330,43 @@ public:
|
||||
SDL_Quit();
|
||||
}
|
||||
|
||||
bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, SDL_AudioCallback callback) {
|
||||
SDL_AudioSpec desired = {};
|
||||
desired.freq = freq;
|
||||
desired.format = AUDIO_S16SYS;
|
||||
desired.samples = 1024;
|
||||
desired.channels = channels;
|
||||
desired.userdata = userdata;
|
||||
desired.callback = callback;
|
||||
|
||||
closeAudioInputDevice();
|
||||
|
||||
SDL_AudioSpec obtained = {};
|
||||
m_sdlAudioInputDevice = SDL_OpenAudioDevice(name, 1, &desired, &obtained, 0);
|
||||
|
||||
if (m_sdlAudioInputDevice) {
|
||||
if (name)
|
||||
Logger::info("Opened audio input device '{}'", name);
|
||||
else
|
||||
Logger::info("Opened default audio input device");
|
||||
SDL_PauseAudioDevice(m_sdlAudioInputDevice, 0);
|
||||
}
|
||||
else
|
||||
Logger::info("Failed to open audio input device: {}", SDL_GetError());
|
||||
|
||||
return m_sdlAudioInputDevice != 0;
|
||||
}
|
||||
|
||||
bool closeAudioInputDevice() {
|
||||
if (m_sdlAudioInputDevice) {
|
||||
Logger::info("Closing audio input device");
|
||||
SDL_CloseAudioDevice(m_sdlAudioInputDevice);
|
||||
m_sdlAudioInputDevice = 0;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
m_cursorCache.ptr(m_currentCursor);
|
||||
m_cursorCache.cleanup();
|
||||
@ -397,7 +443,7 @@ public:
|
||||
Logger::error("Application: threw exception during shutdown: {}", outputException(e, true));
|
||||
}
|
||||
|
||||
SDL_CloseAudioDevice(m_sdlAudioDevice);
|
||||
SDL_CloseAudioDevice(m_sdlAudioOutputDevice);
|
||||
m_SdlControllers.clear();
|
||||
|
||||
SDL_SetCursor(NULL);
|
||||
@ -569,6 +615,14 @@ private:
|
||||
SDL_PauseAudio(true);
|
||||
}
|
||||
|
||||
bool openAudioInputDevice(const char* name, int freq, int channels, void* userdata, AudioCallback callback) override {
|
||||
return parent->openAudioInputDevice(name, freq, channels, userdata, callback);
|
||||
};
|
||||
|
||||
bool closeAudioInputDevice() override {
|
||||
return parent->closeAudioInputDevice();
|
||||
};
|
||||
|
||||
float updateRate() const override {
|
||||
return parent->m_updateRate;
|
||||
}
|
||||
@ -803,7 +857,8 @@ private:
|
||||
|
||||
SDL_Window* m_sdlWindow = nullptr;
|
||||
SDL_GLContext m_sdlGlContext = nullptr;
|
||||
SDL_AudioDeviceID m_sdlAudioDevice = 0;
|
||||
SDL_AudioDeviceID m_sdlAudioOutputDevice = 0;
|
||||
SDL_AudioDeviceID m_sdlAudioInputDevice = 0;
|
||||
|
||||
typedef std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> SDLGameControllerUPtr;
|
||||
StableHashMap<int, SDLGameControllerUPtr> m_SdlControllers;
|
||||
|
@ -221,7 +221,7 @@ void Mixer::stopAll(float rampTime) {
|
||||
p.first->stop(vel);
|
||||
}
|
||||
|
||||
void Mixer::read(int16_t* outBuffer, size_t frameCount) {
|
||||
void Mixer::read(int16_t* outBuffer, size_t frameCount, ExtraMixFunction extraMixFunction) {
|
||||
// Make this method as least locky as possible by copying all the needed
|
||||
// member data before the expensive audio / effect stuff.
|
||||
unsigned sampleRate;
|
||||
@ -326,7 +326,7 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
|
||||
m_mixBuffer[s * channels + c] = 0;
|
||||
} else {
|
||||
for (size_t c = 0; c < channels; ++c)
|
||||
m_mixBuffer[s * channels + c] = m_mixBuffer[s * channels + c] * volume;
|
||||
m_mixBuffer[s * channels + c] *= volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -338,7 +338,8 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
|
||||
float vol = lerp((float)s / frameCount, beginVolume * groupVolume * audioStopVolBegin, endVolume * groupEndVolume * audioStopVolEnd);
|
||||
for (size_t c = 0; c < channels; ++c) {
|
||||
float sample = m_mixBuffer[s * channels + c] * vol * audioState.positionalChannelVolumes[c] * audioInstance->m_volume.value;
|
||||
outBuffer[s * channels + c] = clamp(sample + outBuffer[s * channels + c], -32767.0f, 32767.0f);
|
||||
int16_t& outSample = outBuffer[s * channels + c];
|
||||
outSample = clamp(sample + outSample, -32767.0f, 32767.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,6 +348,9 @@ void Mixer::read(int16_t* outBuffer, size_t frameCount) {
|
||||
}
|
||||
}
|
||||
|
||||
if (extraMixFunction)
|
||||
extraMixFunction(outBuffer, frameCount, channels);
|
||||
|
||||
{
|
||||
MutexLocker locker(m_effectsMutex);
|
||||
// Apply all active effects
|
||||
|
@ -95,6 +95,7 @@ private:
|
||||
// Thread safe mixer class with basic effects support.
|
||||
class Mixer {
|
||||
public:
|
||||
typedef function<void(int16_t* buffer, size_t frames, unsigned channels)> ExtraMixFunction;
|
||||
typedef function<void(int16_t* buffer, size_t frames, unsigned channels)> EffectFunction;
|
||||
typedef function<float(unsigned, Vec2F, float)> PositionalAttenuationFunction;
|
||||
|
||||
@ -126,7 +127,7 @@ public:
|
||||
|
||||
// Reads pending audio data. This is thread safe with the other Mixer
|
||||
// methods, but only one call to read may be active at a time.
|
||||
void read(int16_t* samples, size_t frameCount);
|
||||
void read(int16_t* samples, size_t frameCount, ExtraMixFunction extraMixFunction = {});
|
||||
|
||||
// Call within the main loop of the program using Mixer, calculates
|
||||
// positional attenuation of audio and does cleanup.
|
||||
|
@ -14,9 +14,13 @@
|
||||
#include "StarWorldTemplate.hpp"
|
||||
#include "StarWorldClient.hpp"
|
||||
#include "StarRootLoader.hpp"
|
||||
#include "StarInput.hpp"
|
||||
#include "StarVoice.hpp"
|
||||
#include "StarCurve25519.hpp"
|
||||
|
||||
#include "StarInterfaceLuaBindings.hpp"
|
||||
#include "StarInputLuaBindings.hpp"
|
||||
#include "StarVoiceLuaBindings.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
@ -171,6 +175,7 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController)
|
||||
|
||||
m_guiContext = make_shared<GuiContext>(m_mainMixer->mixer(), appController);
|
||||
m_input = make_shared<Input>();
|
||||
m_voice = make_shared<Voice>(appController);
|
||||
|
||||
auto configuration = m_root->configuration();
|
||||
bool vsync = configuration->get("vsync").toBool();
|
||||
@ -204,6 +209,12 @@ void ClientApplication::applicationInit(ApplicationControllerPtr appController)
|
||||
|
||||
appController->setMaxFrameSkip(assets->json("/client.config:maxFrameSkip").toUInt());
|
||||
appController->setUpdateTrackWindow(assets->json("/client.config:updateTrackWindow").toFloat());
|
||||
|
||||
if (auto jVoice = configuration->get("voice"))
|
||||
m_voice->loadJson(jVoice.toObject(), true);
|
||||
|
||||
m_voice->init();
|
||||
m_voice->setLocalSpeaker(0);
|
||||
}
|
||||
|
||||
void ClientApplication::renderInit(RendererPtr renderer) {
|
||||
@ -366,6 +377,12 @@ void ClientApplication::update() {
|
||||
else if (m_state > MainAppState::Title)
|
||||
updateRunning();
|
||||
|
||||
// Swallow leftover encoded voice data if we aren't in-game to allow mic read to continue for settings.
|
||||
if (m_state <= MainAppState::Title) {
|
||||
DataStreamBuffer ext;
|
||||
m_voice->send(ext);
|
||||
} // TODO: directly disable encoding at menu so we don't have to do this
|
||||
|
||||
m_guiContext->cleanup();
|
||||
m_edgeKeyEvents.clear();
|
||||
m_input->reset();
|
||||
@ -417,8 +434,12 @@ void ClientApplication::render() {
|
||||
}
|
||||
|
||||
void ClientApplication::getAudioData(int16_t* sampleData, size_t frameCount) {
|
||||
if (m_mainMixer)
|
||||
m_mainMixer->read(sampleData, frameCount);
|
||||
if (m_mainMixer) {
|
||||
m_mainMixer->read(sampleData, frameCount, [&](int16_t* buffer, size_t frames, unsigned channels) {
|
||||
if (m_voice)
|
||||
m_voice->mix(buffer, frames, channels);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ClientApplication::changeState(MainAppState newState) {
|
||||
@ -450,6 +471,8 @@ void ClientApplication::changeState(MainAppState newState) {
|
||||
}
|
||||
m_cinematicOverlay->stop();
|
||||
|
||||
m_voice->clearSpeakers();
|
||||
|
||||
if (auto p2pNetworkingService = appController()->p2pNetworkingService()) {
|
||||
p2pNetworkingService->setJoinUnavailable();
|
||||
p2pNetworkingService->setAcceptingP2PConnections(false);
|
||||
@ -483,6 +506,7 @@ void ClientApplication::changeState(MainAppState newState) {
|
||||
m_statistics = make_shared<Statistics>(m_root->toStoragePath("player"), appController()->statisticsService());
|
||||
m_universeClient = make_shared<UniverseClient>(m_playerStorage, m_statistics);
|
||||
m_universeClient->setLuaCallbacks("input", LuaBindings::makeInputCallbacks());
|
||||
m_universeClient->setLuaCallbacks("voice", LuaBindings::makeVoiceCallbacks());
|
||||
|
||||
m_mainMixer->setUniverseClient(m_universeClient);
|
||||
m_titleScreen = make_shared<TitleScreen>(m_playerStorage, m_mainMixer->mixer());
|
||||
@ -601,6 +625,7 @@ void ClientApplication::changeState(MainAppState newState) {
|
||||
m_worldPainter = make_shared<WorldPainter>();
|
||||
m_mainInterface = make_shared<MainInterface>(m_universeClient, m_worldPainter, m_cinematicOverlay);
|
||||
m_universeClient->setLuaCallbacks("interface", LuaBindings::makeInterfaceCallbacks(m_mainInterface.get()));
|
||||
m_universeClient->startLua();
|
||||
|
||||
m_mainMixer->setWorldPainter(m_worldPainter);
|
||||
|
||||
@ -839,13 +864,51 @@ void ClientApplication::updateRunning() {
|
||||
if (checkDisconnection())
|
||||
return;
|
||||
|
||||
m_voice->setInput(m_input->bindHeld("opensb", "pushToTalk"));
|
||||
DataStreamBuffer voiceData;
|
||||
voiceData.setByteOrder(ByteOrder::LittleEndian);
|
||||
//voiceData.writeBytes(VoiceBroadcastPrefix.utf8Bytes()); transmitting with SE compat for now
|
||||
bool needstoSendVoice = m_voice->send(voiceData, 5000);
|
||||
m_universeClient->update();
|
||||
|
||||
if (checkDisconnection())
|
||||
return;
|
||||
|
||||
if (auto worldClient = m_universeClient->worldClient())
|
||||
if (auto worldClient = m_universeClient->worldClient()) {
|
||||
auto& broadcastCallback = worldClient->broadcastCallback();
|
||||
if (!broadcastCallback) {
|
||||
broadcastCallback = [&](PlayerPtr player, StringView broadcast) -> bool {
|
||||
auto& view = broadcast.utf8();
|
||||
if (view.rfind(VoiceBroadcastPrefix.utf8(), 0) != NPos) {
|
||||
auto entityId = player->entityId();
|
||||
auto speaker = m_voice->speaker(connectionForEntity(entityId));
|
||||
speaker->entityId = entityId;
|
||||
speaker->name = player->name();
|
||||
speaker->position = player->mouthPosition();
|
||||
m_voice->receive(speaker, view.substr(VoiceBroadcastPrefix.utf8Size()));
|
||||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
if (worldClient->inWorld()) {
|
||||
if (needstoSendVoice) {
|
||||
auto signature = Curve25519::sign(voiceData.ptr(), voiceData.size());
|
||||
std::string_view signatureView((char*)signature.data(), signature.size());
|
||||
std::string_view audioDataView(voiceData.ptr(), voiceData.size());
|
||||
auto broadcast = strf("data\0voice\0{}{}"s, signatureView, audioDataView);
|
||||
worldClient->sendSecretBroadcast(broadcast, true);
|
||||
}
|
||||
if (auto mainPlayer = m_universeClient->mainPlayer()) {
|
||||
auto localSpeaker = m_voice->localSpeaker();
|
||||
localSpeaker->position = mainPlayer->position();
|
||||
localSpeaker->entityId = mainPlayer->entityId();
|
||||
localSpeaker->name = mainPlayer->name();
|
||||
}
|
||||
m_voice->setLocalSpeaker(worldClient->connection());
|
||||
}
|
||||
worldClient->setInteractiveHighlightMode(isActionTaken(InterfaceAction::ShowLabels));
|
||||
}
|
||||
|
||||
updateCamera();
|
||||
|
||||
|
@ -11,11 +11,13 @@
|
||||
#include "StarErrorScreen.hpp"
|
||||
#include "StarCinematic.hpp"
|
||||
#include "StarKeyBindings.hpp"
|
||||
#include "StarInput.hpp"
|
||||
#include "StarMainApplication.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Input);
|
||||
STAR_CLASS(Voice);
|
||||
|
||||
class ClientApplication : public Application {
|
||||
protected:
|
||||
virtual void startup(StringList const& cmdLineArgs) override;
|
||||
@ -76,6 +78,7 @@ private:
|
||||
MainMixerPtr m_mainMixer;
|
||||
GuiContextPtr m_guiContext;
|
||||
InputPtr m_input;
|
||||
VoicePtr m_voice;
|
||||
|
||||
// Valid after renderInit is called the first time
|
||||
CinematicPtr m_cinematicOverlay;
|
||||
|
@ -21,6 +21,7 @@ SET (star_core_HEADERS
|
||||
StarColor.hpp
|
||||
StarCompression.hpp
|
||||
StarConfig.hpp
|
||||
StarCurve25519.hpp
|
||||
StarDataStream.hpp
|
||||
StarDataStreamDevices.hpp
|
||||
StarDataStreamExtra.hpp
|
||||
@ -133,6 +134,7 @@ SET (star_core_SOURCES
|
||||
StarByteArray.cpp
|
||||
StarColor.cpp
|
||||
StarCompression.cpp
|
||||
StarCurve25519.cpp
|
||||
StarDataStream.cpp
|
||||
StarDataStreamDevices.cpp
|
||||
StarDirectives.cpp
|
||||
|
@ -44,6 +44,7 @@ using std::mem_fn;
|
||||
using std::ref;
|
||||
using std::cref;
|
||||
using namespace std::placeholders;
|
||||
using namespace std::string_literals;
|
||||
|
||||
using std::prev;
|
||||
// using std::next;
|
||||
|
48
source/core/StarCurve25519.cpp
Normal file
@ -0,0 +1,48 @@
|
||||
#include "StarCurve25519.hpp"
|
||||
#include "StarRandom.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
|
||||
#include "curve25519/include/curve25519_dh.h"
|
||||
#include "curve25519/include/ed25519_signature.h"
|
||||
|
||||
namespace Star::Curve25519 {
|
||||
|
||||
struct KeySet {
|
||||
PrivateKey privateKey;
|
||||
PublicKey publicKey;
|
||||
|
||||
KeySet() {
|
||||
SecretKey secret;
|
||||
Random::randBytes(SecretKeySize).copyTo((char*)secret.data());
|
||||
|
||||
secret[0] &= 248;
|
||||
secret[31] &= 127;
|
||||
secret[31] |= 64;
|
||||
|
||||
ed25519_CreateKeyPair(publicKey.data(), privateKey.data(), nullptr, secret.data());
|
||||
}
|
||||
};
|
||||
|
||||
static KeySet const& staticKeys() {
|
||||
static KeySet keys;
|
||||
|
||||
return keys;
|
||||
}
|
||||
|
||||
PrivateKey const& privateKey() { return staticKeys().privateKey; }
|
||||
|
||||
|
||||
|
||||
Signature sign(void* data, size_t len) {
|
||||
Signature signature;
|
||||
ed25519_SignMessage(signature.data(), privateKey().data(), nullptr, (unsigned char*)data, len);
|
||||
return signature;
|
||||
}
|
||||
|
||||
bool verify(uint8_t const* signature, uint8_t const* publicKey, void* data, size_t len) {
|
||||
return ed25519_VerifySignature(signature, publicKey, (unsigned char*)data, len);
|
||||
}
|
||||
|
||||
PublicKey const& publicKey() { return staticKeys().publicKey; }
|
||||
|
||||
}
|
25
source/core/StarCurve25519.hpp
Normal file
@ -0,0 +1,25 @@
|
||||
#ifndef STAR_CURVE_25519_HPP
|
||||
#define STAR_CURVE_25519_HPP
|
||||
#include "StarEncode.hpp"
|
||||
#include "StarByteArray.hpp"
|
||||
#include "StarArray.hpp"
|
||||
|
||||
namespace Star::Curve25519 {
|
||||
|
||||
constexpr size_t PublicKeySize = 32;
|
||||
constexpr size_t SecretKeySize = 32;
|
||||
constexpr size_t PrivateKeySize = 64;
|
||||
constexpr size_t SignatureSize = 64;
|
||||
|
||||
typedef Array<uint8_t, PublicKeySize> PublicKey;
|
||||
typedef Array<uint8_t, SecretKeySize> SecretKey;
|
||||
typedef Array<uint8_t, PrivateKeySize> PrivateKey;
|
||||
typedef Array<uint8_t, SignatureSize> Signature;
|
||||
|
||||
PublicKey const& publicKey();
|
||||
Signature sign(void* data, size_t len);
|
||||
bool verify(uint8_t const* signature, uint8_t const* publicKey, void* data, size_t len);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -205,7 +205,7 @@ size_t DataStream::readVlqU(uint64_t& i) {
|
||||
size_t bytesRead = Star::readVlqU(i, makeFunctionInputIterator([this]() { return this->read<uint8_t>(); }));
|
||||
|
||||
if (bytesRead == NPos)
|
||||
throw DataStreamException("Error reading VLQ encoded intenger!");
|
||||
throw DataStreamException("Error reading VLQ encoded integer!");
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
@ -214,7 +214,7 @@ size_t DataStream::readVlqI(int64_t& i) {
|
||||
size_t bytesRead = Star::readVlqI(i, makeFunctionInputIterator([this]() { return this->read<uint8_t>(); }));
|
||||
|
||||
if (bytesRead == NPos)
|
||||
throw DataStreamException("Error reading VLQ encoded intenger!");
|
||||
throw DataStreamException("Error reading VLQ encoded integer!");
|
||||
|
||||
return bytesRead;
|
||||
}
|
||||
|
@ -164,4 +164,11 @@ void DataStreamExternalBuffer::reset(char const* externalData, size_t len) {
|
||||
m_buffer.reset(externalData, len);
|
||||
}
|
||||
|
||||
void DataStreamExternalBuffer::readData(char* data, size_t len) {
|
||||
m_buffer.readFull(data, len);
|
||||
}
|
||||
void DataStreamExternalBuffer::writeData(char const* data, size_t len) {
|
||||
m_buffer.writeFull(data, len);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -140,6 +140,9 @@ public:
|
||||
|
||||
void reset(char const* externalData, size_t len);
|
||||
|
||||
void readData(char* data, size_t len) override;
|
||||
void writeData(char const* data, size_t len) override;
|
||||
|
||||
private:
|
||||
ExternalBuffer m_buffer;
|
||||
};
|
||||
|
@ -1095,6 +1095,17 @@ LuaFunction LuaEngine::createRawFunction(lua_CFunction function) {
|
||||
return LuaFunction(LuaDetail::LuaHandle(RefPtr<LuaEngine>(this), popHandle(m_state)));
|
||||
}
|
||||
|
||||
LuaFunction LuaEngine::createFunctionFromSource(int handleIndex, char const* contents, size_t size, char const* name) {
|
||||
lua_checkstack(m_state, 2);
|
||||
|
||||
handleError(m_state, luaL_loadbuffer(m_state, contents, size, name));
|
||||
|
||||
pushHandle(m_state, handleIndex);
|
||||
lua_setupvalue(m_state, -2, 1);
|
||||
|
||||
return LuaFunction(LuaDetail::LuaHandle(RefPtr<LuaEngine>(this), popHandle(m_state)));
|
||||
}
|
||||
|
||||
void LuaEngine::pushLuaValue(lua_State* state, LuaValue const& luaValue) {
|
||||
lua_checkstack(state, 1);
|
||||
|
||||
|
@ -310,6 +310,7 @@ public:
|
||||
using LuaTable::contains;
|
||||
using LuaTable::remove;
|
||||
using LuaTable::engine;
|
||||
using LuaTable::handleIndex;
|
||||
|
||||
// Splits the path by '.' character, so can get / set values in tables inside
|
||||
// other tables. If any table in the path is not a table but is accessed as
|
||||
@ -530,6 +531,8 @@ public:
|
||||
|
||||
LuaFunction createRawFunction(lua_CFunction func);
|
||||
|
||||
LuaFunction createFunctionFromSource(int handleIndex, char const* contents, size_t size, char const* name);
|
||||
|
||||
LuaThread createThread();
|
||||
|
||||
template <typename T>
|
||||
|
28
source/extern/CMakeLists.txt
vendored
@ -1,10 +1,29 @@
|
||||
SET (OPUS_INSTALL_PKG_CONFIG_MODULE OFF)
|
||||
SET (OPUS_INSTALL_CMAKE_CONFIG_MODULE OFF)
|
||||
SET (OPUS_X86_MAY_HAVE_AVX OFF)
|
||||
SET (OPUS_X86_MAY_HAVE_SSE4_1 OFF)
|
||||
SET (OPUS_ENABLE_FLOAT_API ON)
|
||||
SET (OPUS_STACK_PROTECTOR OFF)
|
||||
SET (OPUS_NONTHREADSAFE_PSEUDOSTACK OFF)
|
||||
SET (OPUS_USE_ALLOCA ON)
|
||||
|
||||
ADD_SUBDIRECTORY (opus)
|
||||
|
||||
IF (OPUS_NONTHREADSAFE_PSEUDOSTACK)
|
||||
MESSAGE (FATAL_ERROR "Opus should not be using NONTHREADSAFE_PSEUDOSTACK")
|
||||
ENDIF ()
|
||||
|
||||
INCLUDE_DIRECTORIES (
|
||||
${STAR_EXTERN_INCLUDES}
|
||||
opus/include
|
||||
fmt
|
||||
lua
|
||||
)
|
||||
|
||||
SET (star_extern_HEADERS
|
||||
curve25519/include/curve25519_dh.h
|
||||
curve25519/include/ed25519_signature.h
|
||||
curve25519/include/external_calls.h
|
||||
fmt/core.h
|
||||
fmt/format.h
|
||||
fmt/format-inl.h
|
||||
@ -22,6 +41,14 @@ SET (star_extern_HEADERS
|
||||
)
|
||||
|
||||
SET (star_extern_SOURCES
|
||||
curve25519/source/sha512.c
|
||||
curve25519/source/curve25519_dh.c
|
||||
curve25519/source/curve25519_mehdi.c
|
||||
curve25519/source/curve25519_order.c
|
||||
curve25519/source/curve25519_utils.c
|
||||
curve25519/source/custom_blind.c
|
||||
curve25519/source/ed25519_sign.c
|
||||
curve25519/source/ed25519_verify.c
|
||||
xxhash.c
|
||||
fmt/format.cc
|
||||
lua/lapi.c
|
||||
@ -60,3 +87,4 @@ SET (star_extern_SOURCES
|
||||
)
|
||||
|
||||
ADD_LIBRARY (star_extern OBJECT ${star_extern_SOURCES} ${star_extern_HEADERS})
|
||||
TARGET_LINK_LIBRARIES(star_extern PUBLIC opus)
|
53
source/extern/curve25519/include/curve25519_dh.h
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __curve25519_dh_key_exchange_h__
|
||||
#define __curve25519_dh_key_exchange_h__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Return public key associated with sk */
|
||||
/* sk will be trimmed on return */
|
||||
void curve25519_dh_CalculatePublicKey(
|
||||
unsigned char *pk, /* [32-bytes] OUT: Public key */
|
||||
unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */
|
||||
|
||||
/* Faster alternative for curve25519_dh_CalculatePublicKey */
|
||||
/* sk will be trimmed on return */
|
||||
void curve25519_dh_CalculatePublicKey_fast(
|
||||
unsigned char *pk, /* [32-bytes] OUT: Public key */
|
||||
unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */
|
||||
|
||||
/* sk will be trimmed on return */
|
||||
void curve25519_dh_CreateSharedKey(
|
||||
unsigned char *shared, /* [32-bytes] OUT: Created shared key */
|
||||
const unsigned char *pk, /* [32-bytes] IN: Other side's public key */
|
||||
unsigned char *sk); /* [32-bytes] IN/OUT: Your secret key */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __curve25519_dh_key_exchange_h__ */
|
98
source/extern/curve25519/include/ed25519_signature.h
vendored
Normal file
@ -0,0 +1,98 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __ed25519_signature_h__
|
||||
#define __ed25519_signature_h__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* -- ed25519-sign ------------------------------------------------------------- */
|
||||
|
||||
#define ed25519_public_key_size 32
|
||||
#define ed25519_secret_key_size 32
|
||||
#define ed25519_private_key_size 64
|
||||
#define ed25519_signature_size 64
|
||||
|
||||
/* Generate public key associated with the secret key */
|
||||
void ed25519_CreateKeyPair(
|
||||
unsigned char *pubKey, /* OUT: public key */
|
||||
unsigned char *privKey, /* OUT: private key */
|
||||
const void *blinding, /* IN: [optional] null or blinding context */
|
||||
const unsigned char *sk); /* IN: secret key (32 bytes) */
|
||||
|
||||
/* Generate message signature */
|
||||
void ed25519_SignMessage(
|
||||
unsigned char *signature, /* OUT:[64 bytes] signature (R,S) */
|
||||
const unsigned char *privKey, /* IN: [64 bytes] private key (sk,pk) */
|
||||
const void *blinding, /* IN: [optional] null or blinding context */
|
||||
const unsigned char *msg, /* IN: [msg_size bytes] message to sign */
|
||||
size_t msg_size); /* IN: size of message */
|
||||
|
||||
void *ed25519_Blinding_Init(
|
||||
void *context, /* IO: null or ptr blinding context */
|
||||
const unsigned char *seed, /* IN: [size bytes] random blinding seed */
|
||||
size_t size); /* IN: size of blinding seed */
|
||||
|
||||
void ed25519_Blinding_Finish(
|
||||
void *context); /* IN: blinding context */
|
||||
|
||||
/* -- ed25519-verify ----------------------------------------------------------- */
|
||||
|
||||
/* Single-phased signature validation.
|
||||
Returns 1 for SUCCESS and 0 for FAILURE
|
||||
*/
|
||||
int ed25519_VerifySignature(
|
||||
const unsigned char *signature, /* IN: [64 bytes] signature (R,S) */
|
||||
const unsigned char *publicKey, /* IN: [32 bytes] public key */
|
||||
const unsigned char *msg, /* IN: [msg_size bytes] message to sign */
|
||||
size_t msg_size); /* IN: size of message */
|
||||
|
||||
/* First part of two-phase signature validation.
|
||||
This function creates context specifc to a given public key.
|
||||
Needs to be called once per public key
|
||||
*/
|
||||
void * ed25519_Verify_Init(
|
||||
void *context, /* IO: null or verify context to use */
|
||||
const unsigned char *publicKey); /* IN: [32 bytes] public key */
|
||||
|
||||
/* Second part of two-phase signature validation.
|
||||
Input context is output of ed25519_Verify_Init() for associated public key.
|
||||
Call it once for each message/signature pairs
|
||||
Returns 1 for SUCCESS and 0 for FAILURE
|
||||
*/
|
||||
int ed25519_Verify_Check(
|
||||
const void *context, /* IN: created by ed25519_Verify_Init */
|
||||
const unsigned char *signature, /* IN: signature (R,S) */
|
||||
const unsigned char *msg, /* IN: message to sign */
|
||||
size_t msg_size); /* IN: size of message */
|
||||
|
||||
/* Free up context memory */
|
||||
void ed25519_Verify_Finish(void *ctx);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __ed25519_signature_h__ */
|
36
source/extern/curve25519/include/external_calls.h
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __external_calls_h__
|
||||
#define __external_calls_h__
|
||||
|
||||
#include <memory.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define mem_alloc(size) malloc(size)
|
||||
#define mem_free(addr) free(addr)
|
||||
#define mem_clear(addr,size) memset(addr,0,size)
|
||||
#define mem_fill(addr,data,size) memset(addr,data,size)
|
||||
|
||||
#endif /* __external_calls_h__ */
|
121
source/extern/curve25519/source/BaseTypes.h
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __curve25519_base_type_h__
|
||||
#define __curve25519_base_type_h__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Little-endian as default */
|
||||
#ifndef ECP_CONFIG_BIG_ENDIAN
|
||||
#define ECP_CONFIG_LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
typedef unsigned char U8;
|
||||
typedef signed char S8;
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
typedef unsigned __int16 U16;
|
||||
typedef signed __int16 S16;
|
||||
typedef unsigned __int32 U32;
|
||||
typedef signed __int32 S32;
|
||||
typedef unsigned __int64 U64;
|
||||
typedef signed __int64 S64;
|
||||
#else
|
||||
typedef uint16_t U16;
|
||||
typedef int16_t S16;
|
||||
typedef uint32_t U32;
|
||||
typedef int32_t S32;
|
||||
typedef uint64_t U64;
|
||||
typedef int64_t S64;
|
||||
#endif
|
||||
|
||||
typedef unsigned int SZ;
|
||||
|
||||
#ifdef ECP_CONFIG_BIG_ENDIAN
|
||||
typedef union
|
||||
{
|
||||
U16 u16;
|
||||
S16 s16;
|
||||
U8 bytes[2];
|
||||
struct { U8 b1, b0; } u8;
|
||||
struct { S8 b1; U8 b0; } s8;
|
||||
} M16;
|
||||
typedef union
|
||||
{
|
||||
U32 u32;
|
||||
S32 s32;
|
||||
U8 bytes[4];
|
||||
struct { U16 w1, w0; } u16;
|
||||
struct { S16 w1; U16 w0; } s16;
|
||||
struct { U8 b3, b2, b1, b0; } u8;
|
||||
struct { M16 hi, lo; } m16;
|
||||
} M32;
|
||||
typedef union
|
||||
{
|
||||
U64 u64;
|
||||
S64 s64;
|
||||
U8 bytes[8];
|
||||
struct { U32 hi, lo; } u32;
|
||||
struct { S32 hi; U32 lo; } s32;
|
||||
struct { U16 w3, w2, w1, w0; } u16;
|
||||
struct { U8 b7, b6, b5, b4, b3, b2, b1, b0; } u8;
|
||||
struct { M32 hi, lo; } m32;
|
||||
} M64;
|
||||
#else
|
||||
typedef union
|
||||
{
|
||||
U16 u16;
|
||||
S16 s16;
|
||||
U8 bytes[2];
|
||||
struct { U8 b0, b1; } u8;
|
||||
struct { U8 b0; S8 b1; } s8;
|
||||
} M16;
|
||||
typedef union
|
||||
{
|
||||
U32 u32;
|
||||
S32 s32;
|
||||
U8 bytes[4];
|
||||
struct { U16 w0, w1; } u16;
|
||||
struct { U16 w0; S16 w1; } s16;
|
||||
struct { U8 b0, b1, b2, b3; } u8;
|
||||
struct { M16 lo, hi; } m16;
|
||||
} M32;
|
||||
typedef union
|
||||
{
|
||||
U64 u64;
|
||||
S64 s64;
|
||||
U8 bytes[8];
|
||||
struct { U32 lo, hi; } u32;
|
||||
struct { U32 lo; S32 hi; } s32;
|
||||
struct { U16 w0, w1, w2, w3; } u16;
|
||||
struct { U8 b0, b1, b2, b3, b4, b5, b6, b7; } u8;
|
||||
struct { M32 lo, hi; } m32;
|
||||
} M64;
|
||||
#endif
|
||||
|
||||
#define IN
|
||||
#define OUT
|
||||
|
||||
#endif
|
1288
source/extern/curve25519/source/base_folding8.h
vendored
Normal file
208
source/extern/curve25519/source/curve25519_dh.c
vendored
Normal file
@ -0,0 +1,208 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "../include/external_calls.h"
|
||||
#include "curve25519_mehdi.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
U_WORD X[K_WORDS]; /* x = X/Z */
|
||||
U_WORD Z[K_WORDS]; /* */
|
||||
} XZ_POINT;
|
||||
|
||||
extern const U_WORD _w_P[K_WORDS];
|
||||
extern EDP_BLINDING_CTX edp_custom_blinding;
|
||||
|
||||
/* x coordinate of base point */
|
||||
const U8 ecp_BasePoint[32] = {
|
||||
9,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 };
|
||||
/* Y = X + X */
|
||||
void ecp_MontDouble(XZ_POINT *Y, const XZ_POINT *X)
|
||||
{
|
||||
U_WORD A[K_WORDS], B[K_WORDS];
|
||||
/* x2 = (x+z)^2 * (x-z)^2 */
|
||||
/* z2 = ((x+z)^2 - (x-z)^2)*((x+z)^2 + ((A-2)/4)((x+z)^2 - (x-z)^2)) */
|
||||
ecp_AddReduce(A, X->X, X->Z); /* A = (x+z) */
|
||||
ecp_SubReduce(B, X->X, X->Z); /* B = (x-z) */
|
||||
ecp_SqrReduce(A, A); /* A = (x+z)^2 */
|
||||
ecp_SqrReduce(B, B); /* B = (x-z)^2 */
|
||||
ecp_MulReduce(Y->X, A, B); /* x2 = (x+z)^2 * (x-z)^2 */
|
||||
ecp_SubReduce(B, A, B); /* B = (x+z)^2 - (x-z)^2 */
|
||||
/* (486662-2)/4 = 121665 */
|
||||
ecp_WordMulAddReduce(A, A, 121665, B);
|
||||
ecp_MulReduce(Y->Z, A, B); /* z2 = (B)*((x+z)^2 + ((A-2)/4)(B)) */
|
||||
}
|
||||
|
||||
/* return P = P + Q, Q = 2Q */
|
||||
void ecp_Mont(XZ_POINT *P, XZ_POINT *Q, IN const U_WORD *Base)
|
||||
{
|
||||
U_WORD A[K_WORDS], B[K_WORDS], C[K_WORDS], D[K_WORDS], E[K_WORDS];
|
||||
/* x3 = ((x1-z1)(x2+z2) + (x1+z1)(x2-z2))^2*zb zb=1 */
|
||||
/* z3 = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2*xb xb=Base */
|
||||
ecp_SubReduce(A, P->X, P->Z); /* A = x1-z1 */
|
||||
ecp_AddReduce(B, P->X, P->Z); /* B = x1+z1 */
|
||||
ecp_SubReduce(C, Q->X, Q->Z); /* C = x2-z2 */
|
||||
ecp_AddReduce(D, Q->X, Q->Z); /* D = x2+z2 */
|
||||
ecp_MulReduce(A, A, D); /* A = (x1-z1)(x2+z2) */
|
||||
ecp_MulReduce(B, B, C); /* B = (x1+z1)(x2-z2) */
|
||||
ecp_AddReduce(E, A, B); /* E = (x1-z1)(x2+z2) + (x1+z1)(x2-z2) */
|
||||
ecp_SubReduce(B, A, B); /* B = (x1-z1)(x2+z2) - (x1+z1)(x2-z2) */
|
||||
ecp_SqrReduce(P->X, E); /* x3 = ((x1-z1)(x2+z2) + (x1+z1)(x2-z2))^2 */
|
||||
ecp_SqrReduce(A, B); /* A = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2 */
|
||||
ecp_MulReduce(P->Z, A, Base); /* z3 = ((x1-z1)(x2+z2) - (x1+z1)(x2-z2))^2*Base */
|
||||
|
||||
/* x4 = (x2+z2)^2 * (x2-z2)^2 */
|
||||
/* z4 = ((x2+z2)^2 - (x2-z2)^2)*((x2+z2)^2 + 121665((x2+z2)^2 - (x2-z2)^2)) */
|
||||
/* C = (x2-z2) */
|
||||
/* D = (x2+z2) */
|
||||
ecp_SqrReduce(A, D); /* A = (x2+z2)^2 */
|
||||
ecp_SqrReduce(B, C); /* B = (x2-z2)^2 */
|
||||
ecp_MulReduce(Q->X, A, B); /* x4 = (x2+z2)^2 * (x2-z2)^2 */
|
||||
ecp_SubReduce(B, A, B); /* B = (x2+z2)^2 - (x2-z2)^2 */
|
||||
ecp_WordMulAddReduce(A, A, 121665, B);
|
||||
ecp_MulReduce(Q->Z, A, B); /* z4 = B*((x2+z2)^2 + 121665*B) */
|
||||
}
|
||||
|
||||
/* Constant-time measure: */
|
||||
/* Use different set of parameters for bit=0 or bit=1 with no conditional jump */
|
||||
/* */
|
||||
#define ECP_MONT(n) j = (k >> n) & 1; ecp_Mont(PP[j], QP[j], X)
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Return point Q = k*P */
|
||||
/* K in a little-endian byte array */
|
||||
void ecp_PointMultiply(
|
||||
OUT U8 *PublicKey,
|
||||
IN const U8 *BasePoint,
|
||||
IN const U8 *SecretKey,
|
||||
IN int len)
|
||||
{
|
||||
int i, j, k;
|
||||
U_WORD X[K_WORDS];
|
||||
XZ_POINT P, Q, *PP[2], *QP[2];
|
||||
|
||||
ecp_BytesToWords(X, BasePoint);
|
||||
|
||||
/* 1: P = (2k+1)G, Q = (2k+2)G */
|
||||
/* 0: Q = (2k+1)G, P = (2k)G */
|
||||
|
||||
/* Find first non-zero bit */
|
||||
while (len-- > 0)
|
||||
{
|
||||
k = SecretKey[len];
|
||||
for (i = 0; i < 8; i++, k <<= 1)
|
||||
{
|
||||
/* P = kG, Q = (k+1)G */
|
||||
if (k & 0x80)
|
||||
{
|
||||
/* We have first non-zero bit
|
||||
// This is always bit 254 for keys created according to the spec.
|
||||
// Start with randomized base point
|
||||
*/
|
||||
|
||||
ecp_Add(P.Z, X, edp_custom_blinding.zr); /* P.Z = random */
|
||||
ecp_MulReduce(P.X, X, P.Z);
|
||||
ecp_MontDouble(&Q, &P);
|
||||
|
||||
PP[1] = &P; PP[0] = &Q;
|
||||
QP[1] = &Q; QP[0] = &P;
|
||||
|
||||
/* Everything we reference in the below loop are on the stack
|
||||
// and already touched (cached)
|
||||
*/
|
||||
|
||||
while (++i < 8) { k <<= 1; ECP_MONT(7); }
|
||||
while (len > 0)
|
||||
{
|
||||
k = SecretKey[--len];
|
||||
ECP_MONT(7);
|
||||
ECP_MONT(6);
|
||||
ECP_MONT(5);
|
||||
ECP_MONT(4);
|
||||
ECP_MONT(3);
|
||||
ECP_MONT(2);
|
||||
ECP_MONT(1);
|
||||
ECP_MONT(0);
|
||||
}
|
||||
|
||||
ecp_Inverse(Q.Z, P.Z);
|
||||
ecp_MulMod(X, P.X, Q.Z);
|
||||
ecp_WordsToBytes(PublicKey, X);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* K is 0 */
|
||||
mem_fill(PublicKey, 0, 32);
|
||||
}
|
||||
|
||||
/* -- DH key exchange interfaces ----------------------------------------- */
|
||||
|
||||
/* Return R = a*P where P is curve25519 base point */
|
||||
void x25519_BasePointMultiply(OUT U8 *r, IN const U8 *sk)
|
||||
{
|
||||
Ext_POINT S;
|
||||
U_WORD t[K_WORDS];
|
||||
|
||||
ecp_BytesToWords(t, sk);
|
||||
edp_BasePointMult(&S, t, edp_custom_blinding.zr);
|
||||
|
||||
/* Convert ed25519 point to x25519 point */
|
||||
|
||||
/* u = (1 + y)/(1 - y) = (Z + Y)/(Z - Y) */
|
||||
|
||||
ecp_AddReduce(S.t, S.z, S.y);
|
||||
ecp_SubReduce(S.z, S.z, S.y);
|
||||
ecp_Inverse(S.z, S.z);
|
||||
ecp_MulMod(S.t, S.t, S.z);
|
||||
ecp_WordsToBytes(r, S.t);
|
||||
}
|
||||
|
||||
/* Return public key associated with sk */
|
||||
void curve25519_dh_CalculatePublicKey_fast(
|
||||
unsigned char *pk, /* [32-bytes] OUT: Public key */
|
||||
unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */
|
||||
{
|
||||
ecp_TrimSecretKey(sk);
|
||||
/* Use faster method */
|
||||
x25519_BasePointMultiply(pk, sk);
|
||||
}
|
||||
|
||||
/* Return public key associated with sk */
|
||||
void curve25519_dh_CalculatePublicKey(
|
||||
unsigned char *pk, /* [32-bytes] OUT: Public key */
|
||||
unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */
|
||||
{
|
||||
ecp_TrimSecretKey(sk);
|
||||
ecp_PointMultiply(pk, ecp_BasePoint, sk, 32);
|
||||
}
|
||||
|
||||
/* Create a shared secret */
|
||||
void curve25519_dh_CreateSharedKey(
|
||||
unsigned char *shared, /* [32-bytes] OUT: Created shared key */
|
||||
const unsigned char *pk, /* [32-bytes] IN: Other side's public key */
|
||||
unsigned char *sk) /* [32-bytes] IN/OUT: Your secret key */
|
||||
{
|
||||
ecp_TrimSecretKey(sk);
|
||||
ecp_PointMultiply(shared, pk, sk, 32);
|
||||
}
|
410
source/extern/curve25519/source/curve25519_mehdi.c
vendored
Normal file
@ -0,0 +1,410 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include "../include/external_calls.h"
|
||||
#include "curve25519_mehdi.h"
|
||||
|
||||
/*
|
||||
The curve used is y2 = x^3 + 486662x^2 + x, a Montgomery curve, over
|
||||
the prime field defined by the prime number 2^255 - 19, and it uses the
|
||||
base point x = 9.
|
||||
Protocol uses compressed elliptic point (only X coordinates), so it
|
||||
allows for efficient use of the Montgomery ladder for ECDH, using only
|
||||
XZ coordinates.
|
||||
|
||||
The curve is birationally equivalent to Ed25519 (Twisted Edwards curve).
|
||||
|
||||
b = 256
|
||||
p = 2**255 - 19
|
||||
l = 2**252 + 27742317777372353535851937790883648493
|
||||
|
||||
This library is a constant-time implementation of field operations
|
||||
*/
|
||||
|
||||
typedef struct
|
||||
{
|
||||
U32 X[8]; /* x = X/Z */
|
||||
U32 Z[8]; /* */
|
||||
} XZ_POINT;
|
||||
|
||||
const U32 _w_P[8] = {
|
||||
0xFFFFFFED,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,
|
||||
0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0x7FFFFFFF
|
||||
};
|
||||
|
||||
/* Maximum number of prime p that fits into 256-bits */
|
||||
const U32 _w_maxP[8] = { /* 2*P < 2**256 */
|
||||
0xFFFFFFDA,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,
|
||||
0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF,0xFFFFFFFF
|
||||
};
|
||||
|
||||
void ecp_SetValue(U32* X, U32 value)
|
||||
{
|
||||
X[0] = value;
|
||||
X[1] = X[2] = X[3] = X[4] = X[5] = X[6] = X[7] = 0;
|
||||
}
|
||||
|
||||
/* Y = X */
|
||||
void ecp_Copy(U32* Y, const U32* X)
|
||||
{
|
||||
memcpy(Y, X, 8*sizeof(U32));
|
||||
}
|
||||
|
||||
int ecp_CmpNE(const U32* X, const U32* Y)
|
||||
{
|
||||
return ((X[0] ^ Y[0]) | (X[1] ^ Y[1]) | (X[2] ^ Y[2]) | (X[3] ^ Y[3]) |
|
||||
(X[4] ^ Y[4]) | (X[5] ^ Y[5]) | (X[6] ^ Y[6]) | (X[7] ^ Y[7]));
|
||||
}
|
||||
|
||||
int ecp_CmpLT(const U32* X, const U32* Y)
|
||||
{
|
||||
U32 T[8];
|
||||
return ecp_Sub(T, X, Y);
|
||||
}
|
||||
|
||||
#define ECP_ADD_C0(Y,X,V) c.u64 = (U64)(X) + (V); Y = c.u32.lo;
|
||||
#define ECP_ADD_C1(Y,X) c.u64 = (U64)(X) + c.u32.hi; Y = c.u32.lo;
|
||||
|
||||
#define ECP_SUB_C0(Y,X,V) c.s64 = (U64)(X) - (V); Y = c.u32.lo;
|
||||
#define ECP_SUB_C1(Y,X) c.s64 = (U64)(X) + (S64)c.s32.hi; Y = c.u32.lo;
|
||||
|
||||
#define ECP_MULSET_W0(Y,b,X) c.u64 = (U64)(b)*(X); Y = c.u32.lo;
|
||||
#define ECP_MULSET_W1(Y,b,X) c.u64 = (U64)(b)*(X) + c.u32.hi; Y = c.u32.lo;
|
||||
|
||||
#define ECP_MULADD_W0(Z,Y,b,X) c.u64 = (U64)(b)*(X) + (Y); Z = c.u32.lo;
|
||||
#define ECP_MULADD_W1(Z,Y,b,X) c.u64 = (U64)(b)*(X) + (U64)(Y) + c.u32.hi; Z = c.u32.lo;
|
||||
|
||||
#define ECP_ADD32(Z,X,Y) c.u64 = (U64)(X) + (Y); Z = c.u32.lo;
|
||||
#define ECP_ADC32(Z,X,Y) c.u64 = (U64)(X) + (U64)(Y) + c.u32.hi; Z = c.u32.lo;
|
||||
#define ECP_SUB32(Z,X,Y) b.s64 = (S64)(X) - (Y); Z = b.s32.lo;
|
||||
#define ECP_SBC32(Z,X,Y) b.s64 = (S64)(X) - (U64)(Y) + b.s32.hi; Z = b.s32.lo;
|
||||
|
||||
/* Computes Z = X+Y */
|
||||
U32 ecp_Add(U32* Z, const U32* X, const U32* Y)
|
||||
{
|
||||
M64 c;
|
||||
|
||||
ECP_ADD32(Z[0], X[0], Y[0]);
|
||||
ECP_ADC32(Z[1], X[1], Y[1]);
|
||||
ECP_ADC32(Z[2], X[2], Y[2]);
|
||||
ECP_ADC32(Z[3], X[3], Y[3]);
|
||||
ECP_ADC32(Z[4], X[4], Y[4]);
|
||||
ECP_ADC32(Z[5], X[5], Y[5]);
|
||||
ECP_ADC32(Z[6], X[6], Y[6]);
|
||||
ECP_ADC32(Z[7], X[7], Y[7]);
|
||||
return c.u32.hi;
|
||||
}
|
||||
|
||||
/* Computes Z = X-Y */
|
||||
S32 ecp_Sub(U32* Z, const U32* X, const U32* Y)
|
||||
{
|
||||
M64 b;
|
||||
ECP_SUB32(Z[0], X[0], Y[0]);
|
||||
ECP_SBC32(Z[1], X[1], Y[1]);
|
||||
ECP_SBC32(Z[2], X[2], Y[2]);
|
||||
ECP_SBC32(Z[3], X[3], Y[3]);
|
||||
ECP_SBC32(Z[4], X[4], Y[4]);
|
||||
ECP_SBC32(Z[5], X[5], Y[5]);
|
||||
ECP_SBC32(Z[6], X[6], Y[6]);
|
||||
ECP_SBC32(Z[7], X[7], Y[7]);
|
||||
return b.s32.hi;
|
||||
}
|
||||
|
||||
/* Computes Z = X+Y mod P */
|
||||
void ecp_AddReduce(U32* Z, const U32* X, const U32* Y)
|
||||
{
|
||||
M64 c;
|
||||
c.u32.hi = ecp_Add(Z, X, Y) * 38;
|
||||
|
||||
/* Z += c.u32.hi * 38 */
|
||||
ECP_ADD_C0(Z[0], Z[0], c.u32.hi);
|
||||
ECP_ADD_C1(Z[1], Z[1]);
|
||||
ECP_ADD_C1(Z[2], Z[2]);
|
||||
ECP_ADD_C1(Z[3], Z[3]);
|
||||
ECP_ADD_C1(Z[4], Z[4]);
|
||||
ECP_ADD_C1(Z[5], Z[5]);
|
||||
ECP_ADD_C1(Z[6], Z[6]);
|
||||
ECP_ADD_C1(Z[7], Z[7]);
|
||||
|
||||
/* One more carry at most */
|
||||
ECP_ADD_C0(Z[0], Z[0], c.u32.hi*38);
|
||||
ECP_ADD_C1(Z[1], Z[1]);
|
||||
ECP_ADD_C1(Z[2], Z[2]);
|
||||
ECP_ADD_C1(Z[3], Z[3]);
|
||||
ECP_ADD_C1(Z[4], Z[4]);
|
||||
ECP_ADD_C1(Z[5], Z[5]);
|
||||
ECP_ADD_C1(Z[6], Z[6]);
|
||||
ECP_ADD_C1(Z[7], Z[7]);
|
||||
}
|
||||
|
||||
/* Computes Z = X-Y mod P */
|
||||
void ecp_SubReduce(U32* Z, const U32* X, const U32* Y)
|
||||
{
|
||||
M64 c;
|
||||
c.u32.hi = ecp_Sub(Z, X, Y) & 38;
|
||||
|
||||
ECP_SUB_C0(Z[0], Z[0], c.u32.hi);
|
||||
ECP_SUB_C1(Z[1], Z[1]);
|
||||
ECP_SUB_C1(Z[2], Z[2]);
|
||||
ECP_SUB_C1(Z[3], Z[3]);
|
||||
ECP_SUB_C1(Z[4], Z[4]);
|
||||
ECP_SUB_C1(Z[5], Z[5]);
|
||||
ECP_SUB_C1(Z[6], Z[6]);
|
||||
ECP_SUB_C1(Z[7], Z[7]);
|
||||
|
||||
ECP_SUB_C0(Z[0], Z[0], c.u32.hi & 38);
|
||||
ECP_SUB_C1(Z[1], Z[1]);
|
||||
ECP_SUB_C1(Z[2], Z[2]);
|
||||
ECP_SUB_C1(Z[3], Z[3]);
|
||||
ECP_SUB_C1(Z[4], Z[4]);
|
||||
ECP_SUB_C1(Z[5], Z[5]);
|
||||
ECP_SUB_C1(Z[6], Z[6]);
|
||||
ECP_SUB_C1(Z[7], Z[7]);
|
||||
}
|
||||
|
||||
void ecp_Mod(U32 *X)
|
||||
{
|
||||
U32 T[8];
|
||||
U32 c = (U32)ecp_Sub(X, X, _w_P);
|
||||
|
||||
/* set T = 0 if c=0, else T = P */
|
||||
|
||||
T[0] = c & 0xFFFFFFED;
|
||||
T[1] = T[2] = T[3] = T[4] = T[5] = T[6] = c;
|
||||
T[7] = c >> 1;
|
||||
|
||||
ecp_Add(X, X, T); /* X += 0 or P */
|
||||
|
||||
/* In case there is another P there */
|
||||
|
||||
c = (U32)ecp_Sub(X, X, _w_P);
|
||||
|
||||
/* set T = 0 if c=0, else T = P */
|
||||
|
||||
T[0] = c & 0xFFFFFFED;
|
||||
T[1] = T[2] = T[3] = T[4] = T[5] = T[6] = c;
|
||||
T[7] = c >> 1;
|
||||
|
||||
ecp_Add(X, X, T); /* X += 0 or P */
|
||||
}
|
||||
|
||||
/* Computes Y = b*X */
|
||||
static void ecp_mul_set(U32* Y, U32 b, const U32* X)
|
||||
{
|
||||
M64 c;
|
||||
ECP_MULSET_W0(Y[0], b, X[0]);
|
||||
ECP_MULSET_W1(Y[1], b, X[1]);
|
||||
ECP_MULSET_W1(Y[2], b, X[2]);
|
||||
ECP_MULSET_W1(Y[3], b, X[3]);
|
||||
ECP_MULSET_W1(Y[4], b, X[4]);
|
||||
ECP_MULSET_W1(Y[5], b, X[5]);
|
||||
ECP_MULSET_W1(Y[6], b, X[6]);
|
||||
ECP_MULSET_W1(Y[7], b, X[7]);
|
||||
Y[8] = c.u32.hi;
|
||||
}
|
||||
|
||||
/* Computes Y += b*X */
|
||||
/* Addition is performed on lower 8-words of Y */
|
||||
static void ecp_mul_add(U32* Y, U32 b, const U32* X)
|
||||
{
|
||||
M64 c;
|
||||
ECP_MULADD_W0(Y[0], Y[0], b, X[0]);
|
||||
ECP_MULADD_W1(Y[1], Y[1], b, X[1]);
|
||||
ECP_MULADD_W1(Y[2], Y[2], b, X[2]);
|
||||
ECP_MULADD_W1(Y[3], Y[3], b, X[3]);
|
||||
ECP_MULADD_W1(Y[4], Y[4], b, X[4]);
|
||||
ECP_MULADD_W1(Y[5], Y[5], b, X[5]);
|
||||
ECP_MULADD_W1(Y[6], Y[6], b, X[6]);
|
||||
ECP_MULADD_W1(Y[7], Y[7], b, X[7]);
|
||||
Y[8] = c.u32.hi;
|
||||
}
|
||||
|
||||
/* Computes Z = Y + b*X and return carry */
|
||||
void ecp_WordMulAddReduce(U32 *Z, const U32* Y, U32 b, const U32* X)
|
||||
{
|
||||
M64 c;
|
||||
ECP_MULADD_W0(Z[0], Y[0], b, X[0]);
|
||||
ECP_MULADD_W1(Z[1], Y[1], b, X[1]);
|
||||
ECP_MULADD_W1(Z[2], Y[2], b, X[2]);
|
||||
ECP_MULADD_W1(Z[3], Y[3], b, X[3]);
|
||||
ECP_MULADD_W1(Z[4], Y[4], b, X[4]);
|
||||
ECP_MULADD_W1(Z[5], Y[5], b, X[5]);
|
||||
ECP_MULADD_W1(Z[6], Y[6], b, X[6]);
|
||||
ECP_MULADD_W1(Z[7], Y[7], b, X[7]);
|
||||
|
||||
/* Z += c.u32.hi * 38 */
|
||||
ECP_MULADD_W0(Z[0], Z[0], c.u32.hi, 38);
|
||||
ECP_ADD_C1(Z[1], Z[1]);
|
||||
ECP_ADD_C1(Z[2], Z[2]);
|
||||
ECP_ADD_C1(Z[3], Z[3]);
|
||||
ECP_ADD_C1(Z[4], Z[4]);
|
||||
ECP_ADD_C1(Z[5], Z[5]);
|
||||
ECP_ADD_C1(Z[6], Z[6]);
|
||||
ECP_ADD_C1(Z[7], Z[7]);
|
||||
|
||||
/* One more time at most */
|
||||
ECP_MULADD_W0(Z[0], Z[0], c.u32.hi, 38);
|
||||
ECP_ADD_C1(Z[1], Z[1]);
|
||||
ECP_ADD_C1(Z[2], Z[2]);
|
||||
ECP_ADD_C1(Z[3], Z[3]);
|
||||
ECP_ADD_C1(Z[4], Z[4]);
|
||||
ECP_ADD_C1(Z[5], Z[5]);
|
||||
ECP_ADD_C1(Z[6], Z[6]);
|
||||
ECP_ADD_C1(Z[7], Z[7]);
|
||||
}
|
||||
|
||||
/* Computes Z = X*Y mod P. */
|
||||
/* Output fits into 8 words but could be greater than P */
|
||||
void ecp_MulReduce(U32* Z, const U32* X, const U32* Y)
|
||||
{
|
||||
U32 T[16];
|
||||
|
||||
ecp_mul_set(T+0, X[0], Y);
|
||||
ecp_mul_add(T+1, X[1], Y);
|
||||
ecp_mul_add(T+2, X[2], Y);
|
||||
ecp_mul_add(T+3, X[3], Y);
|
||||
ecp_mul_add(T+4, X[4], Y);
|
||||
ecp_mul_add(T+5, X[5], Y);
|
||||
ecp_mul_add(T+6, X[6], Y);
|
||||
ecp_mul_add(T+7, X[7], Y);
|
||||
|
||||
/* We have T = X*Y, now do the reduction in size */
|
||||
|
||||
ecp_WordMulAddReduce(Z, T, 38, T+8);
|
||||
}
|
||||
|
||||
/* Computes Z = X*Y */
|
||||
void ecp_Mul(U32* Z, const U32* X, const U32* Y)
|
||||
{
|
||||
ecp_mul_set(Z+0, X[0], Y);
|
||||
ecp_mul_add(Z+1, X[1], Y);
|
||||
ecp_mul_add(Z+2, X[2], Y);
|
||||
ecp_mul_add(Z+3, X[3], Y);
|
||||
ecp_mul_add(Z+4, X[4], Y);
|
||||
ecp_mul_add(Z+5, X[5], Y);
|
||||
ecp_mul_add(Z+6, X[6], Y);
|
||||
ecp_mul_add(Z+7, X[7], Y);
|
||||
}
|
||||
|
||||
/* Computes Z = X*Y mod P. */
|
||||
void ecp_SqrReduce(U32* Y, const U32* X)
|
||||
{
|
||||
/* TBD: Implementation is based on multiply */
|
||||
/* Optimize for squaring */
|
||||
|
||||
U32 T[16];
|
||||
|
||||
ecp_mul_set(T+0, X[0], X);
|
||||
ecp_mul_add(T+1, X[1], X);
|
||||
ecp_mul_add(T+2, X[2], X);
|
||||
ecp_mul_add(T+3, X[3], X);
|
||||
ecp_mul_add(T+4, X[4], X);
|
||||
ecp_mul_add(T+5, X[5], X);
|
||||
ecp_mul_add(T+6, X[6], X);
|
||||
ecp_mul_add(T+7, X[7], X);
|
||||
|
||||
/* We have T = X*X, now do the reduction in size */
|
||||
|
||||
ecp_WordMulAddReduce(Y, T, 38, T+8);
|
||||
}
|
||||
|
||||
/* Computes Z = X*Y mod P. */
|
||||
void ecp_MulMod(U32* Z, const U32* X, const U32* Y)
|
||||
{
|
||||
ecp_MulReduce(Z, X, Y);
|
||||
ecp_Mod(Z);
|
||||
}
|
||||
|
||||
/* Courtesy of DJB */
|
||||
/* Return out = 1/z mod P */
|
||||
void ecp_Inverse(U32 *out, const U32 *z)
|
||||
{
|
||||
int i;
|
||||
U32 t0[8],t1[8],z2[8],z9[8],z11[8];
|
||||
U32 z2_5_0[8],z2_10_0[8],z2_20_0[8],z2_50_0[8],z2_100_0[8];
|
||||
|
||||
/* 2 */ ecp_SqrReduce(z2,z);
|
||||
/* 4 */ ecp_SqrReduce(t1,z2);
|
||||
/* 8 */ ecp_SqrReduce(t0,t1);
|
||||
/* 9 */ ecp_MulReduce(z9,t0,z);
|
||||
/* 11 */ ecp_MulReduce(z11,z9,z2);
|
||||
/* 22 */ ecp_SqrReduce(t0,z11);
|
||||
/* 2^5 - 2^0 = 31 */ ecp_MulReduce(z2_5_0,t0,z9);
|
||||
|
||||
/* 2^6 - 2^1 */ ecp_SqrReduce(t0,z2_5_0);
|
||||
/* 2^7 - 2^2 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^8 - 2^3 */ ecp_SqrReduce(t0,t1);
|
||||
/* 2^9 - 2^4 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^10 - 2^5 */ ecp_SqrReduce(t0,t1);
|
||||
/* 2^10 - 2^0 */ ecp_MulReduce(z2_10_0,t0,z2_5_0);
|
||||
|
||||
/* 2^11 - 2^1 */ ecp_SqrReduce(t0,z2_10_0);
|
||||
/* 2^12 - 2^2 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) {
|
||||
ecp_SqrReduce(t0,t1);
|
||||
ecp_SqrReduce(t1,t0); }
|
||||
/* 2^20 - 2^0 */ ecp_MulReduce(z2_20_0,t1,z2_10_0);
|
||||
|
||||
/* 2^21 - 2^1 */ ecp_SqrReduce(t0,z2_20_0);
|
||||
/* 2^22 - 2^2 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) {
|
||||
ecp_SqrReduce(t0,t1);
|
||||
ecp_SqrReduce(t1,t0); }
|
||||
/* 2^40 - 2^0 */ ecp_MulReduce(t0,t1,z2_20_0);
|
||||
|
||||
/* 2^41 - 2^1 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^42 - 2^2 */ ecp_SqrReduce(t0,t1);
|
||||
/* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) {
|
||||
ecp_SqrReduce(t1,t0);
|
||||
ecp_SqrReduce(t0,t1); }
|
||||
/* 2^50 - 2^0 */ ecp_MulReduce(z2_50_0,t0,z2_10_0);
|
||||
|
||||
/* 2^51 - 2^1 */ ecp_SqrReduce(t0,z2_50_0);
|
||||
/* 2^52 - 2^2 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) {
|
||||
ecp_SqrReduce(t0,t1);
|
||||
ecp_SqrReduce(t1,t0); }
|
||||
/* 2^100 - 2^0 */ ecp_MulReduce(z2_100_0,t1,z2_50_0);
|
||||
|
||||
/* 2^101 - 2^1 */ ecp_SqrReduce(t1,z2_100_0);
|
||||
/* 2^102 - 2^2 */ ecp_SqrReduce(t0,t1);
|
||||
/* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) {
|
||||
ecp_SqrReduce(t1,t0);
|
||||
ecp_SqrReduce(t0,t1); }
|
||||
/* 2^200 - 2^0 */ ecp_MulReduce(t1,t0,z2_100_0);
|
||||
|
||||
/* 2^201 - 2^1 */ ecp_SqrReduce(t0,t1);
|
||||
/* 2^202 - 2^2 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) {
|
||||
ecp_SqrReduce(t0,t1);
|
||||
ecp_SqrReduce(t1,t0); }
|
||||
/* 2^250 - 2^0 */ ecp_MulReduce(t0,t1,z2_50_0);
|
||||
|
||||
/* 2^251 - 2^1 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^252 - 2^2 */ ecp_SqrReduce(t0,t1);
|
||||
/* 2^253 - 2^3 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^254 - 2^4 */ ecp_SqrReduce(t0,t1);
|
||||
/* 2^255 - 2^5 */ ecp_SqrReduce(t1,t0);
|
||||
/* 2^255 - 21 */ ecp_MulReduce(out,t1,z11);
|
||||
}
|
||||
|
175
source/extern/curve25519/source/curve25519_mehdi.h
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __curve25519_mehdi_h__
|
||||
#define __curve25519_mehdi_h__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "BaseTypes.h"
|
||||
|
||||
#define ECP_VERSION_STR "1.2.0"
|
||||
|
||||
#ifdef USE_ASM_LIB
|
||||
#define U_WORD U64
|
||||
#define S_WORD S64
|
||||
#define WORDSIZE_64
|
||||
#define W64(lo,hi) ((U64)hi<<32)+lo
|
||||
#else
|
||||
#define U_WORD U32
|
||||
#define S_WORD S32
|
||||
#define WORDSIZE_32
|
||||
#define W64(lo,hi) lo,hi
|
||||
#endif
|
||||
|
||||
#define K_BYTES 32
|
||||
#define K_WORDS (K_BYTES/sizeof(U_WORD))
|
||||
|
||||
#define W256(x0,x1,x2,x3,x4,x5,x6,x7) {W64(x0,x1),W64(x2,x3),W64(x4,x5),W64(x6,x7)}
|
||||
|
||||
/* Affine coordinates */
|
||||
typedef struct {
|
||||
U_WORD x[K_WORDS];
|
||||
U_WORD y[K_WORDS];
|
||||
} Affine_POINT;
|
||||
|
||||
/* Projective coordinates */
|
||||
typedef struct {
|
||||
U_WORD x[K_WORDS]; /* x/z */
|
||||
U_WORD y[K_WORDS]; /* y/z */
|
||||
U_WORD z[K_WORDS];
|
||||
U_WORD t[K_WORDS]; /* xy/z */
|
||||
} Ext_POINT;
|
||||
|
||||
/* pre-computed, extended point */
|
||||
typedef struct
|
||||
{
|
||||
U_WORD YpX[K_WORDS]; /* Y+X */
|
||||
U_WORD YmX[K_WORDS]; /* Y-X */
|
||||
U_WORD T2d[K_WORDS]; /* 2d*T */
|
||||
U_WORD Z2[K_WORDS]; /* 2*Z */
|
||||
} PE_POINT;
|
||||
|
||||
/* pre-computed, Affine point */
|
||||
typedef struct
|
||||
{
|
||||
U_WORD YpX[K_WORDS]; /* Y+X */
|
||||
U_WORD YmX[K_WORDS]; /* Y-X */
|
||||
U_WORD T2d[K_WORDS]; /* 2d*T */
|
||||
} PA_POINT;
|
||||
|
||||
typedef struct {
|
||||
U_WORD bl[K_WORDS];
|
||||
U_WORD zr[K_WORDS];
|
||||
PE_POINT BP;
|
||||
} EDP_BLINDING_CTX;
|
||||
|
||||
extern const U8 ecp_BasePoint[K_BYTES];
|
||||
|
||||
/* Return point Q = k*P */
|
||||
void ecp_PointMultiply(OUT U8 *Q, IN const U8 *P, IN const U8 *K, IN int len);
|
||||
|
||||
/* Set low and high bits */
|
||||
void ecp_TrimSecretKey(U8 *X);
|
||||
|
||||
/* -- utils ----------------------------------------------------------------- */
|
||||
|
||||
/* Convert big-endian byte array to little-endian byte array and vice versa */
|
||||
U8* ecp_ReverseByteOrder(OUT U8 *Y, IN const U8 *X);
|
||||
/* Convert little-endian byte array to little-endian word array */
|
||||
U_WORD* ecp_BytesToWords(OUT U_WORD *Y, IN const U8 *X);
|
||||
/* Convert little-endian word array to little-endian byte array */
|
||||
U8* ecp_WordsToBytes(OUT U8 *Y, IN const U_WORD *X);
|
||||
U8* ecp_EncodeInt(OUT U8 *Y, IN const U_WORD *X, IN U8 parity);
|
||||
U8 ecp_DecodeInt(OUT U_WORD *Y, IN const U8 *X);
|
||||
|
||||
/* -- base point order ------------------------------------------------------ */
|
||||
|
||||
/* Z = (X*Y)/R mod BPO */
|
||||
void eco_MontMul(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y);
|
||||
/* Return Y = X*R mod BPO */
|
||||
void eco_ToMont(OUT U_WORD *Y, IN const U_WORD *X);
|
||||
/* Return Y = X/R mod BPO */
|
||||
void eco_FromMont(OUT U_WORD *Y, IN const U_WORD *X);
|
||||
/* Calculate Y = X**E mod BPO */
|
||||
void eco_ExpModBPO(OUT U_WORD *Y, IN const U_WORD *X, IN const U8 *E, IN int bytes);
|
||||
/* Calculate Y = 1/X mod BPO */
|
||||
void eco_InvModBPO(OUT U_WORD *Y, IN const U_WORD *X);
|
||||
/* Z = X*Y mod BPO */
|
||||
void eco_MulReduce(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y);
|
||||
/* Return Y = D mod BPO where D is 512-bit big-endian byte array (i.e SHA512 digest) */
|
||||
void eco_DigestToWords( OUT U_WORD *Y, IN const U8 *D);
|
||||
/* Z = X + Y mod BPO */
|
||||
void eco_AddReduce(OUT U_WORD *Z, IN const U_WORD *X, IN const U_WORD *Y);
|
||||
/* X mod BPO */
|
||||
void eco_Mod(U_WORD *X);
|
||||
|
||||
#define ed25519_PackPoint(buff, Y, parity) ecp_EncodeInt(buff, Y, (U8)(parity & 1))
|
||||
|
||||
/* -- big-number ------------------------------------------------------------ */
|
||||
U_WORD ecp_Add(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
|
||||
S_WORD ecp_Sub(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
|
||||
void ecp_SetValue(U_WORD* X, U_WORD value);
|
||||
void ecp_Copy(U_WORD* Y, const U_WORD* X);
|
||||
void ecp_AddReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
|
||||
void ecp_SubReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
|
||||
void ecp_MulReduce(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
|
||||
void ecp_SqrReduce(U_WORD* Y, const U_WORD* X);
|
||||
void ecp_ModExp2523(U_WORD *Y, const U_WORD *X);
|
||||
void ecp_Inverse(U_WORD *out, const U_WORD *z);
|
||||
void ecp_MulMod(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
|
||||
void ecp_Mul(U_WORD* Z, const U_WORD* X, const U_WORD* Y);
|
||||
/* Computes Y = b*X */
|
||||
void ecp_WordMulSet(U_WORD *Y, U_WORD b, const U_WORD* X);
|
||||
/* Computes Z = Y + b*X and return carry */
|
||||
U_WORD ecp_WordMulAdd(U_WORD *Z, const U_WORD* Y, U_WORD b, const U_WORD* X);
|
||||
/* Computes Z = Y + b*X */
|
||||
void ecp_WordMulAddReduce(U_WORD *Z, const U_WORD* Y, U_WORD b, const U_WORD* X);
|
||||
void ecp_Mod(U_WORD* X);
|
||||
int ecp_CmpNE(const U_WORD* X, const U_WORD* Y);
|
||||
int ecp_CmpLT(const U_WORD* X, const U_WORD* Y);
|
||||
/* Calculate: Y = [b:X] mod BPO */
|
||||
void eco_ReduceHiWord(U_WORD* Y, U_WORD b, const U_WORD* X);
|
||||
|
||||
/* -- ed25519 --------------------------------------------------------------- */
|
||||
void ed25519_UnpackPoint(Affine_POINT *r, const unsigned char *p);
|
||||
void ed25519_CalculateX(OUT U_WORD *X, IN const U_WORD *Y, U_WORD parity);
|
||||
void edp_AddAffinePoint(Ext_POINT *p, const PA_POINT *q);
|
||||
void edp_AddBasePoint(Ext_POINT *p);
|
||||
void edp_AddPoint(Ext_POINT *r, const Ext_POINT *p, const PE_POINT *q);
|
||||
void edp_DoublePoint(Ext_POINT *p);
|
||||
void edp_ComputePermTable(PE_POINT *qtable, Ext_POINT *Q);
|
||||
void edp_ExtPoint2PE(PE_POINT *r, const Ext_POINT *p);
|
||||
void edp_BasePointMult(OUT Ext_POINT *S, IN const U_WORD *sk, IN const U_WORD *R);
|
||||
void edp_BasePointMultiply(OUT Affine_POINT *Q, IN const U_WORD *sk,
|
||||
IN const void *blinding);
|
||||
void ecp_4Folds(U8* Y, const U_WORD* X);
|
||||
void ecp_8Folds(U8* Y, const U_WORD* X);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif /* __curve25519_mehdi_h__ */
|
155
source/extern/curve25519/source/curve25519_order.c
vendored
Normal file
@ -0,0 +1,155 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "curve25519_mehdi.h"
|
||||
|
||||
/*
|
||||
This library provides support for mod BPO (Base Point Order) operations
|
||||
|
||||
BPO = 2**252 + 27742317777372353535851937790883648493
|
||||
BPO = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED
|
||||
|
||||
If you keep adding points together, the result repeats every BPO times.
|
||||
Based on this, you may use:
|
||||
|
||||
public_key = (private_key mod BPO)*BasePoint
|
||||
Split key example:
|
||||
k1 = random()
|
||||
k2 = 1/k1 mod BPO --> k1*k2 = 1 mod BPO
|
||||
P1 = k1*P0 --> P2 = k2*P1 = k2*k1*P0 = P0
|
||||
See selftest code for some examples of BPO usage
|
||||
|
||||
This library is used for implementation of EdDSA sign/verify.
|
||||
*/
|
||||
|
||||
const U_WORD _w_NxBPO[16][K_WORDS] = { /* n*BPO */
|
||||
W256(0,0,0,0,0,0,0,0),
|
||||
W256(0x5CF5D3ED,0x5812631A,0xA2F79CD6,0x14DEF9DE,0,0,0,0x10000000),
|
||||
W256(0xB9EBA7DA,0xB024C634,0x45EF39AC,0x29BDF3BD,0,0,0,0x20000000),
|
||||
W256(0x16E17BC7,0x0837294F,0xE8E6D683,0x3E9CED9B,0,0,0,0x30000000),
|
||||
W256(0x73D74FB4,0x60498C69,0x8BDE7359,0x537BE77A,0,0,0,0x40000000),
|
||||
W256(0xD0CD23A1,0xB85BEF83,0x2ED6102F,0x685AE159,0,0,0,0x50000000),
|
||||
W256(0x2DC2F78E,0x106E529E,0xD1CDAD06,0x7D39DB37,0,0,0,0x60000000),
|
||||
W256(0x8AB8CB7B,0x6880B5B8,0x74C549DC,0x9218D516,0,0,0,0x70000000),
|
||||
W256(0xE7AE9F68,0xC09318D2,0x17BCE6B2,0xA6F7CEF5,0,0,0,0x80000000),
|
||||
W256(0x44A47355,0x18A57BED,0xBAB48389,0xBBD6C8D3,0,0,0,0x90000000),
|
||||
W256(0xA19A4742,0x70B7DF07,0x5DAC205F,0xD0B5C2B2,0,0,0,0xA0000000),
|
||||
W256(0xFE901B2F,0xC8CA4221,0x00A3BD35,0xE594BC91,0,0,0,0xB0000000),
|
||||
W256(0x5B85EF1C,0x20DCA53C,0xA39B5A0C,0xFA73B66F,0,0,0,0xC0000000),
|
||||
W256(0xB87BC309,0x78EF0856,0x4692F6E2,0x0F52B04E,1,0,0,0xD0000000),
|
||||
W256(0x157196F6,0xD1016B71,0xE98A93B8,0x2431AA2C,1,0,0,0xE0000000),
|
||||
W256(0x72676AE3,0x2913CE8B,0x8C82308F,0x3910A40B,1,0,0,0xF0000000)
|
||||
};
|
||||
|
||||
#define minusR_0 0xCF5D3ED0
|
||||
#define minusR_1 0x812631A5
|
||||
#define minusR_2 0x2F79CD65
|
||||
#define minusR_3 0x4DEF9DEA
|
||||
#define minusR_4 1
|
||||
#define minusR_5 0
|
||||
#define minusR_6 0
|
||||
#define minusR_7 0
|
||||
|
||||
/* Calculate: Y = [b:X] mod BPO
|
||||
// For R = 2^256, we calculate Y = b*R + X mod BPO
|
||||
// Since -R mod BPO is only 129-bits, it reduces number of multiplications if
|
||||
// we calculate: Y = X - b*(-R) mod BPO instead
|
||||
// Note that b*(-R) is 161-bits at most and does not need reduction.
|
||||
*/
|
||||
void eco_ReduceHiWord(U32* Y, U32 b, const U32* X)
|
||||
{
|
||||
M64 c;
|
||||
U32 T[8];
|
||||
|
||||
/* Set T = b*(-R) */
|
||||
|
||||
c.u64 = (U64)b*minusR_0;
|
||||
T[0] = c.u32.lo;
|
||||
c.u64 = (U64)b*minusR_1 + c.u32.hi;
|
||||
T[1] = c.u32.lo;
|
||||
c.u64 = (U64)b*minusR_2 + c.u32.hi;
|
||||
T[2] = c.u32.lo;
|
||||
c.u64 = (U64)b*minusR_3 + c.u32.hi;
|
||||
T[3] = c.u32.lo;
|
||||
c.u64 = (U64)b + c.u32.hi;
|
||||
T[4] = c.u32.lo;
|
||||
T[5] = c.u32.hi;
|
||||
T[6] = 0;
|
||||
T[7] = 0;
|
||||
|
||||
/* Y = X - T */
|
||||
c.s32.hi = ecp_Sub(Y, X, T);
|
||||
|
||||
/* Add BPO if there is a borrow */
|
||||
|
||||
ecp_Add(Y, Y, _w_NxBPO[c.s32.hi & 1]);
|
||||
}
|
||||
|
||||
/* Z = X*Y mod BPO */
|
||||
void eco_MulReduce(OUT U32 *Z, IN const U32 *X, IN const U32 *Y)
|
||||
{
|
||||
U32 T[16];
|
||||
ecp_Mul(T, X, Y); /* T = X*Y */
|
||||
eco_ReduceHiWord(T+7, T[15], T+7);
|
||||
eco_ReduceHiWord(T+6, T[14], T+6);
|
||||
eco_ReduceHiWord(T+5, T[13], T+5);
|
||||
eco_ReduceHiWord(T+4, T[12], T+4);
|
||||
eco_ReduceHiWord(T+3, T[11], T+3);
|
||||
eco_ReduceHiWord(T+2, T[10], T+2);
|
||||
eco_ReduceHiWord(T+1, T[9], T+1);
|
||||
eco_ReduceHiWord(Z, T[8], T+0);
|
||||
}
|
||||
|
||||
/* X mod BPO */
|
||||
void eco_Mod(U32 *X)
|
||||
{
|
||||
S32 c = ecp_Sub(X, X, _w_NxBPO[X[7] >> 28]);
|
||||
ecp_Add(X, X, _w_NxBPO[c & 1]);
|
||||
}
|
||||
|
||||
/* Z = X + Y mod BPO */
|
||||
void eco_AddReduce(OUT U32 *Z, IN const U32 *X, IN const U32 *Y)
|
||||
{
|
||||
U32 c = ecp_Add(Z, X, Y);
|
||||
eco_ReduceHiWord(Z, c, Z);
|
||||
}
|
||||
|
||||
/* Return Y = D mod BPO where D is 512-bit message digest (i.e SHA512 digest) */
|
||||
void eco_DigestToWords( OUT U32 *Y, IN const U8 *md)
|
||||
{
|
||||
U32 T[16];
|
||||
|
||||
/* We use digest value as little-endian byte array. */
|
||||
ecp_BytesToWords(T, md);
|
||||
ecp_BytesToWords(T+8, md+32);
|
||||
|
||||
eco_ReduceHiWord(T+7, T[15], T+7);
|
||||
eco_ReduceHiWord(T+6, T[14], T+6);
|
||||
eco_ReduceHiWord(T+5, T[13], T+5);
|
||||
eco_ReduceHiWord(T+4, T[12], T+4);
|
||||
eco_ReduceHiWord(T+3, T[11], T+3);
|
||||
eco_ReduceHiWord(T+2, T[10], T+2);
|
||||
eco_ReduceHiWord(T+1, T[9], T+1);
|
||||
eco_ReduceHiWord(Y, T[8], T+0);
|
||||
}
|
153
source/extern/curve25519/source/curve25519_utils.c
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "curve25519_mehdi.h"
|
||||
|
||||
/* Trim private key */
|
||||
void ecp_TrimSecretKey(U8 *X)
|
||||
{
|
||||
X[0] &= 0xf8;
|
||||
X[31] = (X[31] | 0x40) & 0x7f;
|
||||
}
|
||||
|
||||
/* Convert big-endian byte array to little-endian byte array and vice versa */
|
||||
U8* ecp_ReverseByteOrder(OUT U8 *Y, IN const U8 *X)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 32; i++) Y[i] = X[31-i];
|
||||
return Y;
|
||||
}
|
||||
|
||||
/* Convert little-endian byte array to little-endian word array */
|
||||
U32* ecp_BytesToWords(OUT U32 *Y, IN const U8 *X)
|
||||
{
|
||||
int i;
|
||||
M32 m;
|
||||
|
||||
for (i = 0; i < 8; i++)
|
||||
{
|
||||
m.u8.b0 = *X++;
|
||||
m.u8.b1 = *X++;
|
||||
m.u8.b2 = *X++;
|
||||
m.u8.b3 = *X++;
|
||||
|
||||
Y[i] = m.u32;
|
||||
}
|
||||
return Y;
|
||||
}
|
||||
|
||||
/* Convert little-endian word array to little-endian byte array */
|
||||
U8* ecp_WordsToBytes(OUT U8 *Y, IN const U32 *X)
|
||||
{
|
||||
int i;
|
||||
M32 m;
|
||||
|
||||
for (i = 0; i < 32;)
|
||||
{
|
||||
m.u32 = *X++;
|
||||
Y[i++] = m.u8.b0;
|
||||
Y[i++] = m.u8.b1;
|
||||
Y[i++] = m.u8.b2;
|
||||
Y[i++] = m.u8.b3;
|
||||
}
|
||||
return Y;
|
||||
}
|
||||
|
||||
U8* ecp_EncodeInt(OUT U8 *Y, IN const U32 *X, IN U8 parity)
|
||||
{
|
||||
int i;
|
||||
M32 m;
|
||||
|
||||
for (i = 0; i < 28;)
|
||||
{
|
||||
m.u32 = *X++;
|
||||
Y[i++] = m.u8.b0;
|
||||
Y[i++] = m.u8.b1;
|
||||
Y[i++] = m.u8.b2;
|
||||
Y[i++] = m.u8.b3;
|
||||
}
|
||||
|
||||
m.u32 = *X;
|
||||
Y[28] = m.u8.b0;
|
||||
Y[29] = m.u8.b1;
|
||||
Y[30] = m.u8.b2;
|
||||
Y[31] = (U8)((m.u8.b3 & 0x7f) | (parity << 7));
|
||||
|
||||
return Y;
|
||||
}
|
||||
|
||||
U8 ecp_DecodeInt(OUT U32 *Y, IN const U8 *X)
|
||||
{
|
||||
int i;
|
||||
M32 m;
|
||||
|
||||
for (i = 0; i < 7; i++)
|
||||
{
|
||||
m.u8.b0 = *X++;
|
||||
m.u8.b1 = *X++;
|
||||
m.u8.b2 = *X++;
|
||||
m.u8.b3 = *X++;
|
||||
|
||||
Y[i] = m.u32;
|
||||
}
|
||||
|
||||
m.u8.b0 = *X++;
|
||||
m.u8.b1 = *X++;
|
||||
m.u8.b2 = *X++;
|
||||
m.u8.b3 = *X & 0x7f;
|
||||
|
||||
Y[7] = m.u32;
|
||||
|
||||
return (U8)((*X >> 7) & 1);
|
||||
}
|
||||
|
||||
void ecp_4Folds(U8* Y, const U32* X)
|
||||
{
|
||||
int i, j;
|
||||
U8 a, b;
|
||||
for (i = 32; i-- > 0; Y++)
|
||||
{
|
||||
a = 0;
|
||||
b = 0;
|
||||
for (j = 8; j > 1;)
|
||||
{
|
||||
j -= 2;
|
||||
a = (a << 1) + ((X[j+1] >> i) & 1);
|
||||
b = (b << 1) + ((X[j] >> i) & 1);
|
||||
}
|
||||
Y[0] = a;
|
||||
Y[32] = b;
|
||||
}
|
||||
}
|
||||
|
||||
void ecp_8Folds(U8* Y, const U32* X)
|
||||
{
|
||||
int i, j;
|
||||
U8 a = 0;
|
||||
for (i = 32; i-- > 0;)
|
||||
{
|
||||
for (j = 8; j-- > 0;) a = (a << 1) + ((X[j] >> i) & 1);
|
||||
*Y++ = a;
|
||||
}
|
||||
}
|
27
source/extern/curve25519/source/custom_blind.c
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "curve25519_mehdi.h"
|
||||
#include "custom_blind.h"
|
||||
|
11
source/extern/curve25519/source/custom_blind.h
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
EDP_BLINDING_CTX edp_custom_blinding =
|
||||
{
|
||||
W256(0xDB763B3D,0x2F33DD36,0xF8DC6F74,0x98C50273,0x8216E0D5,0x1AA522E6,0x6BFD2924,0x04BEFBE2),
|
||||
W256(0x73FF0C54,0x459E8BA8,0x84F9550B,0xDBF5F46A,0x6DB0E537,0x65F44FFD,0x2AED4E78,0x2CF92B82),
|
||||
{
|
||||
W256(0xAA6B12E6,0x7E6A2126,0x2E016899,0x80453AE0,0x6F300787,0x7A7E739E,0x264D5BA3,0x4C34AA79),
|
||||
W256(0x52EA83C8,0xE4156040,0x0792FA4C,0x97CA4A43,0x3C9611E1,0x2198FE81,0x41AEAAE6,0x11919AD1),
|
||||
W256(0x54A55516,0xCD8ADA8A,0x820A3D65,0xF85C2DB2,0x06667F3B,0xFF1E45F7,0x26BD6148,0x7F4E9D95),
|
||||
W256(0xA4986374,0x2C1BF6DE,0x57ABC779,0x7C913D04,0xFA1127FA,0x1D7E8692,0xDE0E0680,0xC636320A)
|
||||
}
|
||||
};
|
419
source/extern/curve25519/source/ed25519_sign.c
vendored
Normal file
@ -0,0 +1,419 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "../include/external_calls.h"
|
||||
#include "curve25519_mehdi.h"
|
||||
#include "../include/ed25519_signature.h"
|
||||
#include "sha512.h"
|
||||
|
||||
/*
|
||||
* Arithmetic on twisted Edwards curve y^2 - x^2 = 1 + dx^2y^2
|
||||
* with d = -(121665/121666) mod p
|
||||
* d = 0x52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3
|
||||
* p = 2**255 - 19
|
||||
* p = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED
|
||||
* Base point: y=4/5 mod p
|
||||
* x = 0x216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A
|
||||
* y = 0x6666666666666666666666666666666666666666666666666666666666666658
|
||||
* Base point order:
|
||||
* l = 2**252 + 27742317777372353535851937790883648493
|
||||
* l = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED
|
||||
*/
|
||||
|
||||
extern const U_WORD _w_maxP[K_WORDS];
|
||||
extern const U_WORD _w_NxBPO[16][K_WORDS];
|
||||
|
||||
#define _w_BPO _w_NxBPO[1]
|
||||
|
||||
/*
|
||||
// -- custom blind ---------------------------------------------------------
|
||||
//
|
||||
// edp_custom_blinding is defined in source/custom_blind.c
|
||||
// source/custom_blind is created randomly on every new build
|
||||
//
|
||||
// -------------------------------------------------------------------------
|
||||
*/
|
||||
extern EDP_BLINDING_CTX edp_custom_blinding;
|
||||
|
||||
const U_WORD _w_2d[K_WORDS] = /* 2*d */
|
||||
W256(0x26B2F159,0xEBD69B94,0x8283B156,0x00E0149A,0xEEF3D130,0x198E80F2,0x56DFFCE7,0x2406D9DC);
|
||||
const U_WORD _w_di[K_WORDS] = /* 1/d */
|
||||
W256(0xCDC9F843,0x25E0F276,0x4279542E,0x0B5DD698,0xCDB9CF66,0x2B162114,0x14D5CE43,0x40907ED2);
|
||||
|
||||
#include "base_folding8.h"
|
||||
|
||||
/*
|
||||
Reference: http://eprint.iacr.org/2008/522
|
||||
Cost: 7M + 7add
|
||||
Return: R = P + BasePoint
|
||||
*/
|
||||
void edp_AddBasePoint(Ext_POINT *p)
|
||||
{
|
||||
U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS];
|
||||
|
||||
ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */
|
||||
ecp_MulReduce(a, a, _w_base_folding8[1].YmX);
|
||||
ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */
|
||||
ecp_MulReduce(b, b, _w_base_folding8[1].YpX);
|
||||
ecp_MulReduce(c, p->t, _w_base_folding8[1].T2d); /* C = T1*2d*T2 */
|
||||
ecp_AddReduce(d, p->z, p->z); /* D = 2*Z1 */
|
||||
ecp_SubReduce(e, b, a); /* E = B-A */
|
||||
ecp_AddReduce(b, b, a); /* H = B+A */
|
||||
ecp_SubReduce(a, d, c); /* F = D-C */
|
||||
ecp_AddReduce(d, d, c); /* G = D+C */
|
||||
|
||||
ecp_MulReduce(p->x, e, a); /* E*F */
|
||||
ecp_MulReduce(p->y, b, d); /* H*G */
|
||||
ecp_MulReduce(p->t, e, b); /* E*H */
|
||||
ecp_MulReduce(p->z, d, a); /* G*F */
|
||||
}
|
||||
|
||||
/*
|
||||
Assumptions: pre-computed q, q->Z=1
|
||||
Cost: 7M + 7add
|
||||
Return: P = P + Q
|
||||
*/
|
||||
void edp_AddAffinePoint(Ext_POINT *p, const PA_POINT *q)
|
||||
{
|
||||
U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS];
|
||||
ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */
|
||||
ecp_MulReduce(a, a, q->YmX);
|
||||
ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */
|
||||
ecp_MulReduce(b, b, q->YpX);
|
||||
ecp_MulReduce(c, p->t, q->T2d); /* C = T1*2d*T2 */
|
||||
ecp_AddReduce(d, p->z, p->z); /* D = Z1*2*Z2 (Z2=1)*/
|
||||
ecp_SubReduce(e, b, a); /* E = B-A */
|
||||
ecp_AddReduce(b, b, a); /* H = B+A */
|
||||
ecp_SubReduce(a, d, c); /* F = D-C */
|
||||
ecp_AddReduce(d, d, c); /* G = D+C */
|
||||
|
||||
ecp_MulReduce(p->x, e, a); /* E*F */
|
||||
ecp_MulReduce(p->y, b, d); /* H*G */
|
||||
ecp_MulReduce(p->t, e, b); /* E*H */
|
||||
ecp_MulReduce(p->z, d, a); /* G*F */
|
||||
}
|
||||
|
||||
/*
|
||||
Reference: http://eprint.iacr.org/2008/522
|
||||
Cost: 4M + 4S + 7add
|
||||
Return: P = 2*P
|
||||
*/
|
||||
void edp_DoublePoint(Ext_POINT *p)
|
||||
{
|
||||
U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS];
|
||||
|
||||
ecp_SqrReduce(a, p->x); /* A = X1^2 */
|
||||
ecp_SqrReduce(b, p->y); /* B = Y1^2 */
|
||||
ecp_SqrReduce(c, p->z); /* C = 2*Z1^2 */
|
||||
ecp_AddReduce(c, c, c);
|
||||
ecp_SubReduce(d, _w_maxP, a); /* D = -A */
|
||||
|
||||
ecp_SubReduce(a, d, b); /* H = D-B */
|
||||
ecp_AddReduce(d, d, b); /* G = D+B */
|
||||
ecp_SubReduce(b, d, c); /* F = G-C */
|
||||
ecp_AddReduce(e, p->x, p->y); /* E = (X1+Y1)^2-A-B = (X1+Y1)^2+H */
|
||||
ecp_SqrReduce(e, e);
|
||||
ecp_AddReduce(e, e, a);
|
||||
|
||||
ecp_MulReduce(p->x, e, b); /* E*F */
|
||||
ecp_MulReduce(p->y, a, d); /* H*G */
|
||||
ecp_MulReduce(p->z, d, b); /* G*F */
|
||||
ecp_MulReduce(p->t, e, a); /* E*H */
|
||||
}
|
||||
|
||||
/* -- FOLDING ---------------------------------------------------------------
|
||||
//
|
||||
// The performance boost is achieved by a process that I call it FOLDING.
|
||||
// Folding can be viewed as an extension of Shamir's trick but it is based
|
||||
// on break down of the scalar multiplier of a*P into a polynomial of the
|
||||
// form:
|
||||
//
|
||||
// a*P = SUM(a_i*2^(i*w))*P for i = 0,1,2,...n-1
|
||||
//
|
||||
// a*P = SUM(a_i*P_i)
|
||||
//
|
||||
// where P_i = (2^(i*w))*P
|
||||
// n = number of folds
|
||||
// w = bit-length of a_i
|
||||
//
|
||||
// For folding of 8, 256-bit multiplier 'a' is chopped into 8 limbs of
|
||||
// 32-bits each (a_0, a_1,...a_7). P_0 - P_7 can be pre-calculated and
|
||||
// their 256-different permutations can be cached or hard-coded
|
||||
// directly into the code.
|
||||
// This arrangement combined with double-and-add approach reduces the
|
||||
// number of EC point calculations by a factor of 8. We only need 31
|
||||
// double & add operations.
|
||||
//
|
||||
// +---+---+---+---+---+---+- .... -+---+---+---+---+---+---+
|
||||
// a = (|255|254|253|252|251|250| | 5 | 4 | 3 | 2 | 1 | 0 |)
|
||||
// +---+---+---+---+---+---+- .... -+---+---+---+---+---+---+
|
||||
//
|
||||
// a_i P_i
|
||||
// +---+---+---+ .... -+---+---+---+ ----------
|
||||
// a7 = (|255|254|253| |226|225|224|) * (2**224)*P
|
||||
// +---+---+---+ .... -+---+---+---+
|
||||
// a6 = (|225|224|223| |194|193|192|) * (2**192)*P
|
||||
// +---+---+---+ .... -+---+---+---+
|
||||
// a5 = (|191|190|189| |162|161|160|) * (2**160)*P
|
||||
// +---+---+---+ .... -+---+---+---+
|
||||
// a4 = (|159|158|157| |130|129|128|) * (2**128)*P
|
||||
// +---+---+---+ .... -+---+---+---+
|
||||
// a3 = (|127|126|125| | 98| 97| 96|) * (2**96)*P
|
||||
// +---+---+---+ .... -+---+---+---+
|
||||
// a2 = (| 95| 94| 93| | 66| 65| 64|) * (2**64)*P
|
||||
// +---+---+---+ .... -+---+---+---+
|
||||
// a1 = (| 63| 62| 61| | 34| 33| 32|) * (2**32)*P
|
||||
// +---+---+---+ .... -+---+---+---+
|
||||
// a0 = (| 31| 30| 29| | 2 | 1 | 0 |) * (2**0)*P
|
||||
// +---+---+---+ .... -+---+---+---+
|
||||
// | | | |
|
||||
// | +--+ | +--+
|
||||
// | | | |
|
||||
// V V slices V V
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// |255| |254| |225| |224| P7
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// |225| |224| |193| |192| P6
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// |191| |190| |161| |160| P5
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// |159| |158| |129| |128| P4
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// |127| |126| | 97| | 96| P3
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// | 95| | 94| | 65| | 64| P2
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// | 63| | 62| | 33| | 32| P1
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// | 31| | 30| | 1 | | 0 | P0
|
||||
// +---+ +---+ .... +---+ +---+
|
||||
// cut[]: 0 1 .... 30 31
|
||||
// --------------------------------------------------------------------------
|
||||
// Return S = a*P where P is ed25519 base point and R is random
|
||||
*/
|
||||
void edp_BasePointMult(
|
||||
OUT Ext_POINT *S,
|
||||
IN const U_WORD *sk,
|
||||
IN const U_WORD *R)
|
||||
{
|
||||
int i = 1;
|
||||
U8 cut[32];
|
||||
const PA_POINT *p0;
|
||||
|
||||
ecp_8Folds(cut, sk);
|
||||
|
||||
p0 = &_w_base_folding8[cut[0]];
|
||||
|
||||
ecp_SubReduce(S->x, p0->YpX, p0->YmX); /* 2x */
|
||||
ecp_AddReduce(S->y, p0->YpX, p0->YmX); /* 2y */
|
||||
ecp_MulReduce(S->t, p0->T2d, _w_di); /* 2xy */
|
||||
|
||||
/* Randomize starting point */
|
||||
|
||||
ecp_AddReduce(S->z, R, R); /* Z = 2R */
|
||||
ecp_MulReduce(S->x, S->x, R); /* X = 2xR */
|
||||
ecp_MulReduce(S->t, S->t, R); /* T = 2xyR */
|
||||
ecp_MulReduce(S->y, S->y, R); /* Y = 2yR */
|
||||
|
||||
do
|
||||
{
|
||||
edp_DoublePoint(S);
|
||||
edp_AddAffinePoint(S, &_w_base_folding8[cut[i]]);
|
||||
} while (i++ < 31);
|
||||
}
|
||||
|
||||
void edp_BasePointMultiply(
|
||||
OUT Affine_POINT *R,
|
||||
IN const U_WORD *sk,
|
||||
IN const void *blinding)
|
||||
{
|
||||
Ext_POINT S;
|
||||
U_WORD t[K_WORDS];
|
||||
|
||||
if (blinding)
|
||||
{
|
||||
eco_AddReduce(t, sk, ((EDP_BLINDING_CTX*)blinding)->bl);
|
||||
edp_BasePointMult(&S, t, ((EDP_BLINDING_CTX*)blinding)->zr);
|
||||
edp_AddPoint(&S, &S, &((EDP_BLINDING_CTX*)blinding)->BP);
|
||||
}
|
||||
else
|
||||
{
|
||||
edp_BasePointMult(&S, sk, edp_custom_blinding.zr);
|
||||
}
|
||||
|
||||
ecp_Inverse(S.z, S.z);
|
||||
ecp_MulMod(R->x, S.x, S.z);
|
||||
ecp_MulMod(R->y, S.y, S.z);
|
||||
}
|
||||
|
||||
void edp_ExtPoint2PE(PE_POINT *r, const Ext_POINT *p)
|
||||
{
|
||||
ecp_AddReduce(r->YpX, p->y, p->x);
|
||||
ecp_SubReduce(r->YmX, p->y, p->x);
|
||||
ecp_MulReduce(r->T2d, p->t, _w_2d);
|
||||
ecp_AddReduce(r->Z2, p->z, p->z);
|
||||
}
|
||||
|
||||
/* -- Blinding -------------------------------------------------------------
|
||||
//
|
||||
// Blinding is a measure to protect against side channel attacks.
|
||||
// Blinding randomizes the scalar multiplier.
|
||||
//
|
||||
// Instead of calculating a*P, calculate (a+b mod BPO)*P + B
|
||||
//
|
||||
// Where b = random blinding and B = -b*P
|
||||
//
|
||||
// -------------------------------------------------------------------------
|
||||
*/
|
||||
void *ed25519_Blinding_Init(
|
||||
void *context, /* IO: null or ptr blinding context */
|
||||
const unsigned char *seed, /* IN: [size bytes] random blinding seed */
|
||||
size_t size) /* IN: size of blinding seed */
|
||||
{
|
||||
struct {
|
||||
Ext_POINT T;
|
||||
U_WORD t[K_WORDS];
|
||||
SHA512_CTX H;
|
||||
U8 digest[SHA512_DIGEST_LENGTH];
|
||||
} d;
|
||||
|
||||
EDP_BLINDING_CTX *ctx = (EDP_BLINDING_CTX*)context;
|
||||
|
||||
if (ctx == 0)
|
||||
{
|
||||
ctx = (EDP_BLINDING_CTX*)mem_alloc(sizeof(EDP_BLINDING_CTX));
|
||||
if (ctx == 0) return 0;
|
||||
}
|
||||
|
||||
/* Use edp_custom_blinding to protect generation of the new blinder */
|
||||
|
||||
SHA512_Init(&d.H);
|
||||
SHA512_Update(&d.H, edp_custom_blinding.zr, 32);
|
||||
SHA512_Update(&d.H, seed, size);
|
||||
SHA512_Final(d.digest, &d.H);
|
||||
|
||||
ecp_BytesToWords(ctx->zr, d.digest+32);
|
||||
ecp_BytesToWords(d.t, d.digest);
|
||||
eco_Mod(d.t);
|
||||
ecp_Sub(ctx->bl, _w_BPO, d.t);
|
||||
|
||||
eco_AddReduce(d.t, d.t, edp_custom_blinding.bl);
|
||||
edp_BasePointMult(&d.T, d.t, edp_custom_blinding.zr);
|
||||
edp_AddPoint(&d.T, &d.T, &edp_custom_blinding.BP);
|
||||
|
||||
edp_ExtPoint2PE(&ctx->BP, &d.T);
|
||||
|
||||
/* clear potentially sensitive data */
|
||||
mem_clear (&d, sizeof(d));
|
||||
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void ed25519_Blinding_Finish(
|
||||
void *context) /* IN: blinding context */
|
||||
{
|
||||
if (context)
|
||||
{
|
||||
mem_clear (context, sizeof(EDP_BLINDING_CTX));
|
||||
mem_free (context);
|
||||
}
|
||||
}
|
||||
|
||||
/* Generate public and private key pair associated with the secret key */
|
||||
void ed25519_CreateKeyPair(
|
||||
unsigned char *pubKey, /* OUT: public key */
|
||||
unsigned char *privKey, /* OUT: private key */
|
||||
const void *blinding, /* IN: [optional] null or blinding context */
|
||||
const unsigned char *sk) /* IN: secret key (32 bytes) */
|
||||
{
|
||||
U8 md[SHA512_DIGEST_LENGTH];
|
||||
U_WORD t[K_WORDS];
|
||||
SHA512_CTX H;
|
||||
Affine_POINT Q;
|
||||
|
||||
/* [a:b] = H(sk) */
|
||||
SHA512_Init(&H);
|
||||
SHA512_Update(&H, sk, 32);
|
||||
SHA512_Final(md, &H);
|
||||
ecp_TrimSecretKey(md);
|
||||
|
||||
ecp_BytesToWords(t, md);
|
||||
edp_BasePointMultiply(&Q, t, blinding);
|
||||
ed25519_PackPoint(pubKey, Q.y, Q.x[0]);
|
||||
|
||||
memcpy(privKey, sk, 32);
|
||||
memcpy(privKey+32, pubKey, 32);
|
||||
}
|
||||
|
||||
/*
|
||||
* Generate message signature
|
||||
*/
|
||||
void ed25519_SignMessage(
|
||||
unsigned char *signature, /* OUT: [64 bytes] signature (R,S) */
|
||||
const unsigned char *privKey, /* IN: [64 bytes] private key (sk,pk) */
|
||||
const void *blinding, /* IN: [optional] null or blinding context */
|
||||
const unsigned char *msg, /* IN: [msg_size bytes] message to sign */
|
||||
size_t msg_size)
|
||||
{
|
||||
SHA512_CTX H;
|
||||
Affine_POINT R;
|
||||
U_WORD a[K_WORDS], t[K_WORDS], r[K_WORDS];
|
||||
U8 md[SHA512_DIGEST_LENGTH];
|
||||
|
||||
/* [a:b] = H(sk) */
|
||||
SHA512_Init(&H);
|
||||
SHA512_Update(&H, privKey, 32);
|
||||
SHA512_Final(md, &H);
|
||||
ecp_TrimSecretKey(md); /* a = first 32 bytes */
|
||||
ecp_BytesToWords(a, md);
|
||||
|
||||
/* r = H(b + m) mod BPO */
|
||||
SHA512_Init(&H);
|
||||
SHA512_Update(&H, md+32, 32);
|
||||
SHA512_Update(&H, msg, msg_size);
|
||||
SHA512_Final(md, &H);
|
||||
eco_DigestToWords(r, md);
|
||||
eco_Mod(r); /* r mod BPO */
|
||||
|
||||
/* R = r*P */
|
||||
edp_BasePointMultiply(&R, r, blinding);
|
||||
ed25519_PackPoint(signature, R.y, R.x[0]); /* R part of signature */
|
||||
|
||||
/* S = r + H(encoded(R) + pk + m) * a mod BPO */
|
||||
SHA512_Init(&H);
|
||||
SHA512_Update(&H, signature, 32); /* encoded(R) */
|
||||
SHA512_Update(&H, privKey+32, 32); /* pk */
|
||||
SHA512_Update(&H, msg, msg_size); /* m */
|
||||
SHA512_Final(md, &H);
|
||||
eco_DigestToWords(t, md);
|
||||
|
||||
eco_MulReduce(t, t, a); /* h()*a */
|
||||
eco_AddReduce(t, t, r);
|
||||
eco_Mod(t);
|
||||
ecp_WordsToBytes(signature+32, t); /* S part of signature */
|
||||
|
||||
/* Clear sensitive data */
|
||||
ecp_SetValue(a, 0);
|
||||
ecp_SetValue(r, 0);
|
||||
}
|
313
source/extern/curve25519/source/ed25519_verify.c
vendored
Normal file
@ -0,0 +1,313 @@
|
||||
/* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015 mehdi sotoodeh
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "../include/external_calls.h"
|
||||
#include "curve25519_mehdi.h"
|
||||
#include "../include/ed25519_signature.h"
|
||||
#include "sha512.h"
|
||||
|
||||
/*
|
||||
* Arithmetic on twisted Edwards curve y^2 - x^2 = 1 + dx^2y^2
|
||||
* with d = -(121665/121666) mod p
|
||||
* d = 0x52036CEE2B6FFE738CC740797779E89800700A4D4141D8AB75EB4DCA135978A3
|
||||
* p = 2**255 - 19
|
||||
* p = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFED
|
||||
* Base point: y=4/5 mod p
|
||||
* x = 0x216936D3CD6E53FEC0A4E231FDD6DC5C692CC7609525A7B2C9562D608F25D51A
|
||||
* y = 0x6666666666666666666666666666666666666666666666666666666666666658
|
||||
* Base point order:
|
||||
* l = 2**252 + 27742317777372353535851937790883648493
|
||||
* l = 0x1000000000000000000000000000000014DEF9DEA2F79CD65812631A5CF5D3ED
|
||||
*/
|
||||
|
||||
typedef struct {
|
||||
unsigned char pk[32];
|
||||
PE_POINT q_table[16];
|
||||
} EDP_SIGV_CTX;
|
||||
|
||||
extern const U_WORD _w_P[K_WORDS];
|
||||
extern const U_WORD _w_di[K_WORDS];
|
||||
|
||||
extern const PA_POINT _w_base_folding8[256];
|
||||
extern const U_WORD _w_NxBPO[16][K_WORDS];
|
||||
|
||||
#define _w_BPO _w_NxBPO[1]
|
||||
|
||||
#define _w_Zero _w_base_folding8[0].T2d
|
||||
#define _w_One _w_base_folding8[0].YpX
|
||||
|
||||
const U_WORD _w_I[K_WORDS] = /* sqrt(-1) */
|
||||
W256(0x4A0EA0B0,0xC4EE1B27,0xAD2FE478,0x2F431806,0x3DFBD7A7,0x2B4D0099,0x4FC1DF0B,0x2B832480);
|
||||
|
||||
static const U_WORD _w_d[K_WORDS] =
|
||||
W256(0x135978A3,0x75EB4DCA,0x4141D8AB,0x00700A4D,0x7779E898,0x8CC74079,0x2B6FFE73,0x52036CEE);
|
||||
|
||||
void ed25519_CalculateX(OUT U_WORD *X, IN const U_WORD *Y, U_WORD parity)
|
||||
{
|
||||
U_WORD u[K_WORDS], v[K_WORDS], a[K_WORDS], b[K_WORDS];
|
||||
|
||||
/* Calculate sqrt((y^2 - 1)/(d*y^2 + 1)) */
|
||||
|
||||
ecp_SqrReduce(u, Y); /* u = y^2 */
|
||||
ecp_MulReduce(v, u, _w_d); /* v = dy^2 */
|
||||
ecp_SubReduce(u, u, _w_One); /* u = y^2-1 */
|
||||
ecp_AddReduce(v, v, _w_One); /* v = dy^2+1 */
|
||||
|
||||
/* Calculate: sqrt(u/v) = u*v^3 * (u*v^7)^((p-5)/8) */
|
||||
|
||||
ecp_SqrReduce(b, v);
|
||||
ecp_MulReduce(a, u, b);
|
||||
ecp_MulReduce(a, a, v); /* a = u*v^3 */
|
||||
ecp_SqrReduce(b, b); /* b = v^4 */
|
||||
ecp_MulReduce(b, a, b); /* b = u*v^7 */
|
||||
ecp_ModExp2523(b, b);
|
||||
ecp_MulReduce(X, b, a);
|
||||
|
||||
/* Check if we have correct sqrt, else, multiply by sqrt(-1) */
|
||||
|
||||
ecp_SqrReduce(b, X);
|
||||
ecp_MulReduce(b, b, v);
|
||||
ecp_SubReduce(b, b, u);
|
||||
ecp_Mod(b);
|
||||
if (ecp_CmpNE(b, _w_Zero)) ecp_MulReduce(X, X, _w_I);
|
||||
|
||||
while (ecp_CmpLT(X, _w_P) == 0) ecp_Sub(X, X, _w_P);
|
||||
|
||||
/* match parity */
|
||||
if (((X[0] ^ parity) & 1) != 0)
|
||||
ecp_Sub(X, _w_P, X);
|
||||
}
|
||||
|
||||
void ed25519_UnpackPoint(Affine_POINT *r, const unsigned char *p)
|
||||
{
|
||||
U8 parity = ecp_DecodeInt(r->y, p);
|
||||
ed25519_CalculateX(r->x, r->y, parity);
|
||||
}
|
||||
|
||||
void ecp_SrqMulReduce(U_WORD *Z, const U_WORD *X, int n, const U_WORD *Y)
|
||||
{
|
||||
U_WORD t[K_WORDS];
|
||||
ecp_SqrReduce(t, X);
|
||||
while (n-- > 1) ecp_SqrReduce(t, t);
|
||||
ecp_MulReduce(Z, t, Y);
|
||||
}
|
||||
|
||||
void ecp_ModExp2523(U_WORD *Y, const U_WORD *X)
|
||||
{
|
||||
U_WORD x2[K_WORDS], x9[K_WORDS], x11[K_WORDS], x5[K_WORDS], x10[K_WORDS];
|
||||
U_WORD x20[K_WORDS], x50[K_WORDS], x100[K_WORDS], t[K_WORDS];
|
||||
|
||||
ecp_SqrReduce(x2, X); /* 2 */
|
||||
ecp_SrqMulReduce(x9, x2, 2, X); /* 9 */
|
||||
ecp_MulReduce(x11, x9, x2); /* 11 */
|
||||
ecp_SqrReduce(t, x11); /* 22 */
|
||||
ecp_MulReduce(x5, t, x9); /* 31 = 2^5 - 2^0 */
|
||||
ecp_SrqMulReduce(x10, x5, 5, x5); /* 2^10 - 2^0 */
|
||||
ecp_SrqMulReduce(x20, x10, 10, x10); /* 2^20 - 2^0 */
|
||||
ecp_SrqMulReduce(t, x20, 20, x20); /* 2^40 - 2^0 */
|
||||
ecp_SrqMulReduce(x50, t, 10, x10); /* 2^50 - 2^0 */
|
||||
ecp_SrqMulReduce(x100, x50, 50, x50); /* 2^100 - 2^0 */
|
||||
ecp_SrqMulReduce(t, x100, 100, x100); /* 2^200 - 2^0 */
|
||||
ecp_SrqMulReduce(t, t, 50, x50); /* 2^250 - 2^0 */
|
||||
ecp_SqrReduce(t, t); ecp_SqrReduce(t, t); /* 2^252 - 2^2 */
|
||||
ecp_MulReduce(Y, t, X); /* 2^252 - 3 */
|
||||
}
|
||||
|
||||
/*
|
||||
Assumptions: pre-computed q
|
||||
Cost: 8M + 6add
|
||||
Return: P = P + Q
|
||||
*/
|
||||
void edp_AddPoint(Ext_POINT *r, const Ext_POINT *p, const PE_POINT *q)
|
||||
{
|
||||
U_WORD a[K_WORDS], b[K_WORDS], c[K_WORDS], d[K_WORDS], e[K_WORDS];
|
||||
|
||||
ecp_SubReduce(a, p->y, p->x); /* A = (Y1-X1)*(Y2-X2) */
|
||||
ecp_MulReduce(a, a, q->YmX);
|
||||
ecp_AddReduce(b, p->y, p->x); /* B = (Y1+X1)*(Y2+X2) */
|
||||
ecp_MulReduce(b, b, q->YpX);
|
||||
ecp_MulReduce(c, p->t, q->T2d); /* C = T1*2d*T2 */
|
||||
ecp_MulReduce(d, p->z, q->Z2); /* D = Z1*2*Z2 */
|
||||
ecp_SubReduce(e, b, a); /* E = B-A */
|
||||
ecp_AddReduce(b, b, a); /* H = B+A */
|
||||
ecp_SubReduce(a, d, c); /* F = D-C */
|
||||
ecp_AddReduce(d, d, c); /* G = D+C */
|
||||
|
||||
ecp_MulReduce(r->x, e, a); /* E*F */
|
||||
ecp_MulReduce(r->y, b, d); /* H*G */
|
||||
ecp_MulReduce(r->t, e, b); /* E*H */
|
||||
ecp_MulReduce(r->z, d, a); /* G*F */
|
||||
}
|
||||
|
||||
int ed25519_VerifySignature(
|
||||
const unsigned char *signature, /* IN: signature (R,S) */
|
||||
const unsigned char *publicKey, /* IN: public key */
|
||||
const unsigned char *msg, size_t msg_size) /* IN: message to sign */
|
||||
{
|
||||
EDP_SIGV_CTX ctx;
|
||||
|
||||
ed25519_Verify_Init(&ctx, publicKey);
|
||||
|
||||
return ed25519_Verify_Check(&ctx, signature, msg, msg_size);
|
||||
}
|
||||
|
||||
#define QTABLE_SET(d,s) \
|
||||
edp_AddPoint(&T, &Q, &ctx->q_table[s]); \
|
||||
edp_ExtPoint2PE(&ctx->q_table[d], &T)
|
||||
|
||||
void * ed25519_Verify_Init(
|
||||
void *context, /* IO: null or context buffer to use */
|
||||
const unsigned char *publicKey) /* IN: [32 bytes] public key */
|
||||
{
|
||||
int i;
|
||||
Ext_POINT Q, T;
|
||||
EDP_SIGV_CTX *ctx = (EDP_SIGV_CTX*)context;
|
||||
|
||||
if (ctx == 0) ctx = (EDP_SIGV_CTX*)mem_alloc(sizeof(EDP_SIGV_CTX));
|
||||
|
||||
if (ctx)
|
||||
{
|
||||
memcpy(ctx->pk, publicKey, 32);
|
||||
i = ecp_DecodeInt(Q.y, publicKey);
|
||||
ed25519_CalculateX(Q.x, Q.y, ~i); /* Invert parity for -Q */
|
||||
ecp_MulMod(Q.t, Q.x, Q.y);
|
||||
ecp_SetValue(Q.z, 1);
|
||||
|
||||
/* pre-compute q-table */
|
||||
|
||||
/* Calculate: Q0=Q, Q1=(2^64)*Q, Q2=(2^128)*Q, Q3=(2^192)*Q */
|
||||
|
||||
ecp_SetValue(ctx->q_table[0].YpX, 1); /* -- -- -- -- */
|
||||
ecp_SetValue(ctx->q_table[0].YmX, 1);
|
||||
ecp_SetValue(ctx->q_table[0].T2d, 0);
|
||||
ecp_SetValue(ctx->q_table[0].Z2, 2);
|
||||
|
||||
edp_ExtPoint2PE(&ctx->q_table[1], &Q); /* -- -- -- q0 */
|
||||
|
||||
for (i = 0; i < 64; i++) edp_DoublePoint(&Q);
|
||||
|
||||
edp_ExtPoint2PE(&ctx->q_table[2], &Q); /* -- -- q1 -- */
|
||||
QTABLE_SET(3,1); /* -- -- q1 q0 */
|
||||
|
||||
do edp_DoublePoint(&Q); while (++i < 128);
|
||||
|
||||
edp_ExtPoint2PE(&ctx->q_table[4], &Q); /* -- q2 -- -- */
|
||||
QTABLE_SET(5, 1); /* -- q2 -- q0 */
|
||||
QTABLE_SET(6, 2); /* -- q2 q1 -- */
|
||||
QTABLE_SET(7, 3); /* -- q2 q1 q0 */
|
||||
|
||||
do edp_DoublePoint(&Q); while (++i < 192);
|
||||
|
||||
edp_ExtPoint2PE(&ctx->q_table[8], &Q); /* q3 -- -- -- */
|
||||
QTABLE_SET(9, 1); /* q3 -- -- q0 */
|
||||
QTABLE_SET(10, 2); /* q3 -- q1 -- */
|
||||
QTABLE_SET(11, 3); /* q3 -- q1 q0 */
|
||||
QTABLE_SET(12, 4); /* q3 q2 -- -- */
|
||||
QTABLE_SET(13, 5); /* q3 q2 -- q0 */
|
||||
QTABLE_SET(14, 6); /* q3 q2 q1 -- */
|
||||
QTABLE_SET(15, 7); /* q3 q2 q1 q0 */
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
void ed25519_Verify_Finish(void *ctx)
|
||||
{
|
||||
mem_free(ctx);
|
||||
}
|
||||
|
||||
/*
|
||||
Assumptions: qtable = pre-computed Q
|
||||
Calculate: point R = a*P + b*Q where P is base point
|
||||
*/
|
||||
static void edp_PolyPointMultiply(
|
||||
Affine_POINT *r,
|
||||
const U_WORD *a,
|
||||
const U_WORD *b,
|
||||
const PE_POINT *qtable)
|
||||
{
|
||||
int i = 1;
|
||||
Ext_POINT S;
|
||||
const PE_POINT *q0;
|
||||
U8 u[32], v[64];
|
||||
|
||||
ecp_8Folds(u, a);
|
||||
ecp_4Folds(v, b);
|
||||
|
||||
/* Set initial value of S */
|
||||
q0 = &qtable[v[0]];
|
||||
ecp_SubReduce(S.x, q0->YpX, q0->YmX); /* 2x */
|
||||
ecp_AddReduce(S.y, q0->YpX, q0->YmX); /* 2y */
|
||||
ecp_MulReduce(S.t, q0->T2d, _w_di); /* 2xy */
|
||||
ecp_Copy(S.z, q0->Z2); /* 2z */
|
||||
|
||||
do
|
||||
{ /* 31D + 31A */
|
||||
edp_DoublePoint(&S);
|
||||
edp_AddPoint(&S, &S, &qtable[v[i]]);
|
||||
} while (++i < 32);
|
||||
|
||||
do
|
||||
{ /* 32D + 64A */
|
||||
edp_DoublePoint(&S);
|
||||
edp_AddAffinePoint(&S, &_w_base_folding8[u[i-32]]);
|
||||
edp_AddPoint(&S, &S, &qtable[v[i]]);
|
||||
} while (++i < 64);
|
||||
|
||||
ecp_Inverse(S.z, S.z);
|
||||
ecp_MulMod(r->x, S.x, S.z);
|
||||
ecp_MulMod(r->y, S.y, S.z);
|
||||
}
|
||||
|
||||
/*
|
||||
This function can be used for batch verification.
|
||||
Assumptions: context = ed25519_Verify_Init(pk)
|
||||
|
||||
*/
|
||||
int ed25519_Verify_Check(
|
||||
const void *context, /* IN: precomputes */
|
||||
const unsigned char *signature, /* IN: signature (R,S) */
|
||||
const unsigned char *msg, size_t msg_size) /* IN: message to sign */
|
||||
{
|
||||
SHA512_CTX H;
|
||||
Affine_POINT T;
|
||||
U_WORD h[K_WORDS], s[K_WORDS];
|
||||
U8 md[SHA512_DIGEST_LENGTH];
|
||||
|
||||
/* h = H(enc(R) + pk + m) mod BPO */
|
||||
SHA512_Init(&H);
|
||||
SHA512_Update(&H, signature, 32); /* enc(R) */
|
||||
SHA512_Update(&H, ((EDP_SIGV_CTX*)context)->pk, 32);
|
||||
SHA512_Update(&H, msg, msg_size);
|
||||
SHA512_Final(md, &H);
|
||||
eco_DigestToWords(h, md);
|
||||
eco_Mod(h);
|
||||
|
||||
/* T = s*P + h*(-Q) = (s - h*a)*P = r*P = R */
|
||||
|
||||
ecp_BytesToWords(s, signature+32);
|
||||
edp_PolyPointMultiply(&T, s, h, ((EDP_SIGV_CTX*)context)->q_table);
|
||||
ed25519_PackPoint(md, T.y, T.x[0]);
|
||||
|
||||
return (memcmp(md, signature, 32) == 0) ? 1 : 0;
|
||||
}
|
294
source/extern/curve25519/source/sha512.c
vendored
Normal file
@ -0,0 +1,294 @@
|
||||
/* crypto/sha/sha512.c */
|
||||
/* ====================================================================
|
||||
* Copyright (c) 2004 The OpenSSL Project. All rights reserved
|
||||
* according to the OpenSSL license [found in ../../LICENSE].
|
||||
* ====================================================================
|
||||
*/
|
||||
/*
|
||||
* IMPLEMENTATION NOTES.
|
||||
*
|
||||
* As you might have noticed 32-bit hash algorithms:
|
||||
*
|
||||
* - permit SHA_LONG to be wider than 32-bit (case on CRAY);
|
||||
* - optimized versions implement two transform functions: one operating
|
||||
* on [aligned] data in host byte order and one - on data in input
|
||||
* stream byte order;
|
||||
* - share common byte-order neutral collector and padding function
|
||||
* implementations, ../md32_common.h;
|
||||
*
|
||||
* Neither of the above applies to this SHA-512 implementations. Reasons
|
||||
* [in reverse order] are:
|
||||
*
|
||||
* - it's the only 64-bit hash algorithm for the moment of this writing,
|
||||
* there is no need for common collector/padding implementation [yet];
|
||||
* - by supporting only one transform function [which operates on
|
||||
* *aligned* data in input stream byte order, big-endian in this case]
|
||||
* we minimize burden of maintenance in two ways: a) collector/padding
|
||||
* function is simpler; b) only one transform function to stare at;
|
||||
* - SHA_LONG64 is required to be exactly 64-bit in order to be able to
|
||||
* apply a number of optimizations to mitigate potential performance
|
||||
* penalties caused by previous design decision;
|
||||
*
|
||||
* Caveat lector.
|
||||
*
|
||||
* Implementation relies on the fact that "long long" is 64-bit on
|
||||
* both 32- and 64-bit platforms. If some compiler vendor comes up
|
||||
* with 128-bit long long, adjustment to sha.h would be required.
|
||||
* As this implementation relies on 64-bit integer type, it's totally
|
||||
* inappropriate for platforms which don't support it, most notably
|
||||
* 16-bit platforms.
|
||||
* <appro@fy.chalmers.se>
|
||||
*/
|
||||
#include <string.h>
|
||||
#include "../include/external_calls.h"
|
||||
#include "sha512.h"
|
||||
|
||||
#define UINT64(X) X##ULL
|
||||
|
||||
void SHA512_Transform (SHA512_CTX *ctx, const void *in);
|
||||
|
||||
void SHA512_Init (SHA512_CTX *c)
|
||||
{
|
||||
c->h[0]=UINT64(0x6a09e667f3bcc908);
|
||||
c->h[1]=UINT64(0xbb67ae8584caa73b);
|
||||
c->h[2]=UINT64(0x3c6ef372fe94f82b);
|
||||
c->h[3]=UINT64(0xa54ff53a5f1d36f1);
|
||||
c->h[4]=UINT64(0x510e527fade682d1);
|
||||
c->h[5]=UINT64(0x9b05688c2b3e6c1f);
|
||||
c->h[6]=UINT64(0x1f83d9abfb41bd6b);
|
||||
c->h[7]=UINT64(0x5be0cd19137e2179);
|
||||
|
||||
c->Nl=0;
|
||||
c->Nh=0;
|
||||
c->num=0;
|
||||
c->md_len=SHA512_DIGEST_LENGTH;
|
||||
}
|
||||
|
||||
void SHA512_Final (unsigned char *md, SHA512_CTX *c)
|
||||
{
|
||||
unsigned char *p=(unsigned char *)c->u.p;
|
||||
size_t n=c->num;
|
||||
|
||||
p[n]=0x80; /* There always is a room for one */
|
||||
n++;
|
||||
if (n > (SHA512_CBLOCK-16))
|
||||
mem_fill (p+n,0,SHA512_CBLOCK-n), n=0,
|
||||
SHA512_Transform (c,p);
|
||||
|
||||
mem_fill (p+n,0,SHA512_CBLOCK-16-n);
|
||||
#ifdef ECP_CONFIG_BIG_ENDIAN
|
||||
c->u.d[SHA_LBLOCK-2] = c->Nh;
|
||||
c->u.d[SHA_LBLOCK-1] = c->Nl;
|
||||
#else
|
||||
p[SHA512_CBLOCK-1] = (unsigned char)(c->Nl);
|
||||
p[SHA512_CBLOCK-2] = (unsigned char)(c->Nl>>8);
|
||||
p[SHA512_CBLOCK-3] = (unsigned char)(c->Nl>>16);
|
||||
p[SHA512_CBLOCK-4] = (unsigned char)(c->Nl>>24);
|
||||
p[SHA512_CBLOCK-5] = (unsigned char)(c->Nl>>32);
|
||||
p[SHA512_CBLOCK-6] = (unsigned char)(c->Nl>>40);
|
||||
p[SHA512_CBLOCK-7] = (unsigned char)(c->Nl>>48);
|
||||
p[SHA512_CBLOCK-8] = (unsigned char)(c->Nl>>56);
|
||||
p[SHA512_CBLOCK-9] = (unsigned char)(c->Nh);
|
||||
p[SHA512_CBLOCK-10] = (unsigned char)(c->Nh>>8);
|
||||
p[SHA512_CBLOCK-11] = (unsigned char)(c->Nh>>16);
|
||||
p[SHA512_CBLOCK-12] = (unsigned char)(c->Nh>>24);
|
||||
p[SHA512_CBLOCK-13] = (unsigned char)(c->Nh>>32);
|
||||
p[SHA512_CBLOCK-14] = (unsigned char)(c->Nh>>40);
|
||||
p[SHA512_CBLOCK-15] = (unsigned char)(c->Nh>>48);
|
||||
p[SHA512_CBLOCK-16] = (unsigned char)(c->Nh>>56);
|
||||
#endif
|
||||
|
||||
SHA512_Transform (c,p);
|
||||
|
||||
if (md) for (n=0; n < SHA512_DIGEST_LENGTH/8; n++)
|
||||
{
|
||||
M64 m;
|
||||
m.u64 = c->h[n];
|
||||
*(md++) = m.u8.b7;
|
||||
*(md++) = m.u8.b6;
|
||||
*(md++) = m.u8.b5;
|
||||
*(md++) = m.u8.b4;
|
||||
*(md++) = m.u8.b3;
|
||||
*(md++) = m.u8.b2;
|
||||
*(md++) = m.u8.b1;
|
||||
*(md++) = m.u8.b0;
|
||||
}
|
||||
}
|
||||
|
||||
void SHA512_Update (SHA512_CTX *c, const void *_data, size_t len)
|
||||
{
|
||||
SHA_LONG64 l;
|
||||
unsigned char *p=c->u.p;
|
||||
const unsigned char *data=(const unsigned char *)_data;
|
||||
|
||||
if (len==0) return;
|
||||
|
||||
l = (c->Nl+(((SHA_LONG64)len)<<3))&UINT64(0xffffffffffffffff);
|
||||
if (l < c->Nl) c->Nh++;
|
||||
if (sizeof(len)>=8) c->Nh+=(((SHA_LONG64)len)>>61);
|
||||
c->Nl=l;
|
||||
|
||||
if (c->num != 0)
|
||||
{
|
||||
size_t n = SHA512_CBLOCK - c->num;
|
||||
|
||||
if (len < n)
|
||||
{
|
||||
memcpy (p+c->num,data,len), c->num += (unsigned int)len;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
memcpy (p+c->num,data,n), c->num = 0;
|
||||
len-=n, data+=n;
|
||||
SHA512_Transform (c,p);
|
||||
}
|
||||
}
|
||||
|
||||
while (len >= SHA512_CBLOCK)
|
||||
{
|
||||
SHA512_Transform (c,data);//,len/SHA512_CBLOCK),
|
||||
data += SHA512_CBLOCK;
|
||||
len -= SHA512_CBLOCK;
|
||||
}
|
||||
|
||||
if (len != 0)
|
||||
memcpy (p,data,len), c->num = (int)len;
|
||||
}
|
||||
|
||||
static const SHA_LONG64 K512[80] =
|
||||
{
|
||||
UINT64(0x428a2f98d728ae22),UINT64(0x7137449123ef65cd),
|
||||
UINT64(0xb5c0fbcfec4d3b2f),UINT64(0xe9b5dba58189dbbc),
|
||||
UINT64(0x3956c25bf348b538),UINT64(0x59f111f1b605d019),
|
||||
UINT64(0x923f82a4af194f9b),UINT64(0xab1c5ed5da6d8118),
|
||||
UINT64(0xd807aa98a3030242),UINT64(0x12835b0145706fbe),
|
||||
UINT64(0x243185be4ee4b28c),UINT64(0x550c7dc3d5ffb4e2),
|
||||
UINT64(0x72be5d74f27b896f),UINT64(0x80deb1fe3b1696b1),
|
||||
UINT64(0x9bdc06a725c71235),UINT64(0xc19bf174cf692694),
|
||||
UINT64(0xe49b69c19ef14ad2),UINT64(0xefbe4786384f25e3),
|
||||
UINT64(0x0fc19dc68b8cd5b5),UINT64(0x240ca1cc77ac9c65),
|
||||
UINT64(0x2de92c6f592b0275),UINT64(0x4a7484aa6ea6e483),
|
||||
UINT64(0x5cb0a9dcbd41fbd4),UINT64(0x76f988da831153b5),
|
||||
UINT64(0x983e5152ee66dfab),UINT64(0xa831c66d2db43210),
|
||||
UINT64(0xb00327c898fb213f),UINT64(0xbf597fc7beef0ee4),
|
||||
UINT64(0xc6e00bf33da88fc2),UINT64(0xd5a79147930aa725),
|
||||
UINT64(0x06ca6351e003826f),UINT64(0x142929670a0e6e70),
|
||||
UINT64(0x27b70a8546d22ffc),UINT64(0x2e1b21385c26c926),
|
||||
UINT64(0x4d2c6dfc5ac42aed),UINT64(0x53380d139d95b3df),
|
||||
UINT64(0x650a73548baf63de),UINT64(0x766a0abb3c77b2a8),
|
||||
UINT64(0x81c2c92e47edaee6),UINT64(0x92722c851482353b),
|
||||
UINT64(0xa2bfe8a14cf10364),UINT64(0xa81a664bbc423001),
|
||||
UINT64(0xc24b8b70d0f89791),UINT64(0xc76c51a30654be30),
|
||||
UINT64(0xd192e819d6ef5218),UINT64(0xd69906245565a910),
|
||||
UINT64(0xf40e35855771202a),UINT64(0x106aa07032bbd1b8),
|
||||
UINT64(0x19a4c116b8d2d0c8),UINT64(0x1e376c085141ab53),
|
||||
UINT64(0x2748774cdf8eeb99),UINT64(0x34b0bcb5e19b48a8),
|
||||
UINT64(0x391c0cb3c5c95a63),UINT64(0x4ed8aa4ae3418acb),
|
||||
UINT64(0x5b9cca4f7763e373),UINT64(0x682e6ff3d6b2b8a3),
|
||||
UINT64(0x748f82ee5defb2fc),UINT64(0x78a5636f43172f60),
|
||||
UINT64(0x84c87814a1f0ab72),UINT64(0x8cc702081a6439ec),
|
||||
UINT64(0x90befffa23631e28),UINT64(0xa4506cebde82bde9),
|
||||
UINT64(0xbef9a3f7b2c67915),UINT64(0xc67178f2e372532b),
|
||||
UINT64(0xca273eceea26619c),UINT64(0xd186b8c721c0c207),
|
||||
UINT64(0xeada7dd6cde0eb1e),UINT64(0xf57d4f7fee6ed178),
|
||||
UINT64(0x06f067aa72176fba),UINT64(0x0a637dc5a2c898a6),
|
||||
UINT64(0x113f9804bef90dae),UINT64(0x1b710b35131c471b),
|
||||
UINT64(0x28db77f523047d84),UINT64(0x32caab7b40c72493),
|
||||
UINT64(0x3c9ebe0a15c9bebc),UINT64(0x431d67c49c100d4c),
|
||||
UINT64(0x4cc5d4becb3e42b6),UINT64(0x597f299cfc657e2a),
|
||||
UINT64(0x5fcb6fab3ad6faec),UINT64(0x6c44198c4a475817)
|
||||
};
|
||||
|
||||
#define B(x,j) (((SHA_LONG64)(*(((const unsigned char *)(&x))+j)))<<((7-j)*8))
|
||||
#define PULL64(x) (B(x,0)|B(x,1)|B(x,2)|B(x,3)|B(x,4)|B(x,5)|B(x,6)|B(x,7))
|
||||
#define ROTR(x,s) (((x)>>s) | (x)<<(64-s))
|
||||
|
||||
#define Sigma0(x) (ROTR((x),28) ^ ROTR((x),34) ^ ROTR((x),39))
|
||||
#define Sigma1(x) (ROTR((x),14) ^ ROTR((x),18) ^ ROTR((x),41))
|
||||
#define sigma0(x) (ROTR((x),1) ^ ROTR((x),8) ^ ((x)>>7))
|
||||
#define sigma1(x) (ROTR((x),19) ^ ROTR((x),61) ^ ((x)>>6))
|
||||
|
||||
#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z)))
|
||||
#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z)))
|
||||
|
||||
#define ROUND_00_15(i,a,b,c,d,e,f,g,h) do { \
|
||||
T1 += h + Sigma1(e) + Ch(e,f,g) + K512[i]; \
|
||||
h = Sigma0(a) + Maj(a,b,c); \
|
||||
d += T1; h += T1; } while (0)
|
||||
|
||||
#define ROUND_16_80(i,j,a,b,c,d,e,f,g,h,X) do { \
|
||||
s0 = X[(j+1)&0x0f]; s0 = sigma0(s0); \
|
||||
s1 = X[(j+14)&0x0f]; s1 = sigma1(s1); \
|
||||
T1 = X[(j)&0x0f] += s0 + s1 + X[(j+9)&0x0f]; \
|
||||
ROUND_00_15(i+j,a,b,c,d,e,f,g,h); } while (0)
|
||||
|
||||
void SHA512_Transform (SHA512_CTX *ctx, const void *in)
|
||||
{
|
||||
const SHA_LONG64 *W = (SHA_LONG64*)in;
|
||||
SHA_LONG64 a,b,c,d,e,f,g,h,s0,s1,T1;
|
||||
SHA_LONG64 X[16];
|
||||
int i;
|
||||
|
||||
a = ctx->h[0]; b = ctx->h[1]; c = ctx->h[2]; d = ctx->h[3];
|
||||
e = ctx->h[4]; f = ctx->h[5]; g = ctx->h[6]; h = ctx->h[7];
|
||||
|
||||
#ifdef ECP_CONFIG_BIG_ENDIAN
|
||||
T1 = X[0] = W[0]; ROUND_00_15(0,a,b,c,d,e,f,g,h);
|
||||
T1 = X[1] = W[1]; ROUND_00_15(1,h,a,b,c,d,e,f,g);
|
||||
T1 = X[2] = W[2]; ROUND_00_15(2,g,h,a,b,c,d,e,f);
|
||||
T1 = X[3] = W[3]; ROUND_00_15(3,f,g,h,a,b,c,d,e);
|
||||
T1 = X[4] = W[4]; ROUND_00_15(4,e,f,g,h,a,b,c,d);
|
||||
T1 = X[5] = W[5]; ROUND_00_15(5,d,e,f,g,h,a,b,c);
|
||||
T1 = X[6] = W[6]; ROUND_00_15(6,c,d,e,f,g,h,a,b);
|
||||
T1 = X[7] = W[7]; ROUND_00_15(7,b,c,d,e,f,g,h,a);
|
||||
T1 = X[8] = W[8]; ROUND_00_15(8,a,b,c,d,e,f,g,h);
|
||||
T1 = X[9] = W[9]; ROUND_00_15(9,h,a,b,c,d,e,f,g);
|
||||
T1 = X[10] = W[10]; ROUND_00_15(10,g,h,a,b,c,d,e,f);
|
||||
T1 = X[11] = W[11]; ROUND_00_15(11,f,g,h,a,b,c,d,e);
|
||||
T1 = X[12] = W[12]; ROUND_00_15(12,e,f,g,h,a,b,c,d);
|
||||
T1 = X[13] = W[13]; ROUND_00_15(13,d,e,f,g,h,a,b,c);
|
||||
T1 = X[14] = W[14]; ROUND_00_15(14,c,d,e,f,g,h,a,b);
|
||||
T1 = X[15] = W[15]; ROUND_00_15(15,b,c,d,e,f,g,h,a);
|
||||
#else
|
||||
T1 = X[0] = PULL64(W[0]); ROUND_00_15(0,a,b,c,d,e,f,g,h);
|
||||
T1 = X[1] = PULL64(W[1]); ROUND_00_15(1,h,a,b,c,d,e,f,g);
|
||||
T1 = X[2] = PULL64(W[2]); ROUND_00_15(2,g,h,a,b,c,d,e,f);
|
||||
T1 = X[3] = PULL64(W[3]); ROUND_00_15(3,f,g,h,a,b,c,d,e);
|
||||
T1 = X[4] = PULL64(W[4]); ROUND_00_15(4,e,f,g,h,a,b,c,d);
|
||||
T1 = X[5] = PULL64(W[5]); ROUND_00_15(5,d,e,f,g,h,a,b,c);
|
||||
T1 = X[6] = PULL64(W[6]); ROUND_00_15(6,c,d,e,f,g,h,a,b);
|
||||
T1 = X[7] = PULL64(W[7]); ROUND_00_15(7,b,c,d,e,f,g,h,a);
|
||||
T1 = X[8] = PULL64(W[8]); ROUND_00_15(8,a,b,c,d,e,f,g,h);
|
||||
T1 = X[9] = PULL64(W[9]); ROUND_00_15(9,h,a,b,c,d,e,f,g);
|
||||
T1 = X[10] = PULL64(W[10]); ROUND_00_15(10,g,h,a,b,c,d,e,f);
|
||||
T1 = X[11] = PULL64(W[11]); ROUND_00_15(11,f,g,h,a,b,c,d,e);
|
||||
T1 = X[12] = PULL64(W[12]); ROUND_00_15(12,e,f,g,h,a,b,c,d);
|
||||
T1 = X[13] = PULL64(W[13]); ROUND_00_15(13,d,e,f,g,h,a,b,c);
|
||||
T1 = X[14] = PULL64(W[14]); ROUND_00_15(14,c,d,e,f,g,h,a,b);
|
||||
T1 = X[15] = PULL64(W[15]); ROUND_00_15(15,b,c,d,e,f,g,h,a);
|
||||
#endif
|
||||
|
||||
for (i=16;i<80;i+=16)
|
||||
{
|
||||
ROUND_16_80(i, 0,a,b,c,d,e,f,g,h,X);
|
||||
ROUND_16_80(i, 1,h,a,b,c,d,e,f,g,X);
|
||||
ROUND_16_80(i, 2,g,h,a,b,c,d,e,f,X);
|
||||
ROUND_16_80(i, 3,f,g,h,a,b,c,d,e,X);
|
||||
ROUND_16_80(i, 4,e,f,g,h,a,b,c,d,X);
|
||||
ROUND_16_80(i, 5,d,e,f,g,h,a,b,c,X);
|
||||
ROUND_16_80(i, 6,c,d,e,f,g,h,a,b,X);
|
||||
ROUND_16_80(i, 7,b,c,d,e,f,g,h,a,X);
|
||||
ROUND_16_80(i, 8,a,b,c,d,e,f,g,h,X);
|
||||
ROUND_16_80(i, 9,h,a,b,c,d,e,f,g,X);
|
||||
ROUND_16_80(i,10,g,h,a,b,c,d,e,f,X);
|
||||
ROUND_16_80(i,11,f,g,h,a,b,c,d,e,X);
|
||||
ROUND_16_80(i,12,e,f,g,h,a,b,c,d,X);
|
||||
ROUND_16_80(i,13,d,e,f,g,h,a,b,c,X);
|
||||
ROUND_16_80(i,14,c,d,e,f,g,h,a,b,X);
|
||||
ROUND_16_80(i,15,b,c,d,e,f,g,h,a,X);
|
||||
}
|
||||
|
||||
ctx->h[0] += a; ctx->h[1] += b; ctx->h[2] += c; ctx->h[3] += d;
|
||||
ctx->h[4] += e; ctx->h[5] += f; ctx->h[6] += g; ctx->h[7] += h;
|
||||
}
|
92
source/extern/curve25519/source/sha512.h
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
/* crypto/sha/sha512.h */
|
||||
/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
|
||||
* All rights reserved.
|
||||
*
|
||||
* This package is an SSL implementation written
|
||||
* by Eric Young (eay@cryptsoft.com).
|
||||
* The implementation was written so as to conform with Netscapes SSL.
|
||||
*
|
||||
* This library is free for commercial and non-commercial use as long as
|
||||
* the following conditions are aheared to. The following conditions
|
||||
* apply to all code found in this distribution, be it the RC4, RSA,
|
||||
* lhash, DES, etc., code; not just the SSL code. The SSL documentation
|
||||
* included with this distribution is covered by the same copyright terms
|
||||
* except that the holder is Tim Hudson (tjh@cryptsoft.com).
|
||||
*
|
||||
* Copyright remains Eric Young's, and as such any Copyright notices in
|
||||
* the code are not to be removed.
|
||||
* If this package is used in a product, Eric Young should be given attribution
|
||||
* as the author of the parts of the library used.
|
||||
* This can be in the form of a textual message at program startup or
|
||||
* in documentation (online or textual) provided with the package.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
* 1. Redistributions of source code must retain the copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* 3. All advertising materials mentioning features or use of this software
|
||||
* must display the following acknowledgement:
|
||||
* "This product includes cryptographic software written by
|
||||
* Eric Young (eay@cryptsoft.com)"
|
||||
* The word 'cryptographic' can be left out if the rouines from the library
|
||||
* being used are not cryptographic related :-).
|
||||
* 4. If you include any Windows specific code (or a derivative thereof) from
|
||||
* the apps directory (application code) you must include an acknowledgement:
|
||||
* "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
|
||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
* SUCH DAMAGE.
|
||||
*
|
||||
* The licence and distribution terms for any publically available version or
|
||||
* derivative of this code cannot be changed. i.e. this code cannot simply be
|
||||
* copied and put under another distribution licence
|
||||
* [including the GNU Public Licence.]
|
||||
*/
|
||||
|
||||
#ifndef HEADER_SHA512_H
|
||||
#define HEADER_SHA512_H
|
||||
|
||||
#include "BaseTypes.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SHA512_DIGEST_LENGTH 64
|
||||
#define SHA512_CBLOCK 128 /* SHA-512 treats input data as a
|
||||
* contiguous array of 64 bit
|
||||
* wide big-endian values. */
|
||||
#define SHA_LONG64 U64
|
||||
|
||||
typedef struct SHA512state_st
|
||||
{
|
||||
SHA_LONG64 h[8];
|
||||
SHA_LONG64 Nl,Nh;
|
||||
union {
|
||||
SHA_LONG64 d[8];
|
||||
unsigned char p[SHA512_CBLOCK];
|
||||
} u;
|
||||
unsigned int num, md_len;
|
||||
} SHA512_CTX;
|
||||
|
||||
void SHA512_Init(SHA512_CTX *c);
|
||||
void SHA512_Update(SHA512_CTX *c, const void *data, size_t len);
|
||||
void SHA512_Final(unsigned char *md, SHA512_CTX *c);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#endif
|
1
source/extern/opus
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 9fc8fc4cf432640f284113ba502ee027268b0d9f
|
@ -56,6 +56,9 @@ SET (star_frontend_HEADERS
|
||||
StarStatusPane.hpp
|
||||
StarTeleportDialog.hpp
|
||||
StarWireInterface.hpp
|
||||
StarVoice.hpp
|
||||
StarVoiceLuaBindings.hpp
|
||||
StarVoiceSettingsMenu.hpp
|
||||
)
|
||||
|
||||
SET (star_frontend_SOURCES
|
||||
@ -104,6 +107,9 @@ SET (star_frontend_SOURCES
|
||||
StarStatusPane.cpp
|
||||
StarTeleportDialog.cpp
|
||||
StarWireInterface.cpp
|
||||
StarVoice.cpp
|
||||
StarVoiceLuaBindings.cpp
|
||||
StarVoiceSettingsMenu.cpp
|
||||
)
|
||||
|
||||
ADD_LIBRARY (star_frontend OBJECT ${star_frontend_SOURCES} ${star_frontend_HEADERS})
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "StarAiInterface.hpp"
|
||||
#include "StarQuestInterface.hpp"
|
||||
#include "StarStatistics.hpp"
|
||||
#include "StarInterfaceLuaBindings.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
@ -76,11 +77,10 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
|
||||
|
||||
String allArguments = commandLine.substr(1);
|
||||
String command = allArguments.extract();
|
||||
auto arguments = m_parser.tokenizeToStringList(allArguments);
|
||||
|
||||
StringList result;
|
||||
if (auto builtinCommand = m_builtinCommands.maybe(command)) {
|
||||
result.append((*builtinCommand)(arguments));
|
||||
result.append((*builtinCommand)(allArguments));
|
||||
} else if (auto macroCommand = m_macroCommands.maybe(command)) {
|
||||
for (auto const& c : *macroCommand) {
|
||||
if (c.beginsWith("/"))
|
||||
@ -89,6 +89,10 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
|
||||
result.append(c);
|
||||
}
|
||||
} else {
|
||||
auto player = m_universeClient->mainPlayer();
|
||||
if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), strf("/{}", command), { allArguments }))
|
||||
result.append(messageResult->isType(Json::Type::String) ? *messageResult->stringPtr() : messageResult->repr(1, true));
|
||||
else
|
||||
m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast);
|
||||
}
|
||||
return result;
|
||||
@ -130,7 +134,8 @@ String ClientCommandProcessor::gravity() {
|
||||
return toString(m_universeClient->worldClient()->gravity(m_universeClient->mainPlayer()->position()));
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::debug(StringList const& arguments) {
|
||||
String ClientCommandProcessor::debug(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -168,7 +173,8 @@ String ClientCommandProcessor::asyncLighting() {
|
||||
? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::setGravity(StringList const& arguments) {
|
||||
String ClientCommandProcessor::setGravity(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -198,7 +204,8 @@ String ClientCommandProcessor::monochromeLighting() {
|
||||
return strf("Monochrome lighting {}", monochrome ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::radioMessage(StringList const& arguments) {
|
||||
String ClientCommandProcessor::radioMessage(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -225,7 +232,8 @@ String ClientCommandProcessor::clearCinematics() {
|
||||
return "Player cinematic records cleared!";
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::startQuest(StringList const& arguments) {
|
||||
String ClientCommandProcessor::startQuest(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -234,7 +242,8 @@ String ClientCommandProcessor::startQuest(StringList const& arguments) {
|
||||
return "Quest started";
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::completeQuest(StringList const& arguments) {
|
||||
String ClientCommandProcessor::completeQuest(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -242,7 +251,8 @@ String ClientCommandProcessor::completeQuest(StringList const& arguments) {
|
||||
return strf("Quest {} complete", arguments.at(0));
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::failQuest(StringList const& arguments) {
|
||||
String ClientCommandProcessor::failQuest(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -250,7 +260,8 @@ String ClientCommandProcessor::failQuest(StringList const& arguments) {
|
||||
return strf("Quest {} failed", arguments.at(0));
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::previewNewQuest(StringList const& arguments) {
|
||||
String ClientCommandProcessor::previewNewQuest(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -259,7 +270,8 @@ String ClientCommandProcessor::previewNewQuest(StringList const& arguments) {
|
||||
});
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::previewQuestComplete(StringList const& arguments) {
|
||||
String ClientCommandProcessor::previewQuestComplete(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -268,7 +280,8 @@ String ClientCommandProcessor::previewQuestComplete(StringList const& arguments)
|
||||
});
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::previewQuestFailed(StringList const& arguments) {
|
||||
String ClientCommandProcessor::previewQuestFailed(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -294,7 +307,8 @@ String ClientCommandProcessor::deathCount() {
|
||||
return strf("Total deaths: {}{}", deaths, deaths == 0 ? ". Well done!" : "");
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::cinema(StringList const& arguments) {
|
||||
String ClientCommandProcessor::cinema(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -326,7 +340,8 @@ String ClientCommandProcessor::resetAchievements() {
|
||||
return "Unable to reset achievements";
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::statistic(StringList const& arguments) {
|
||||
String ClientCommandProcessor::statistic(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -337,7 +352,8 @@ String ClientCommandProcessor::statistic(StringList const& arguments) {
|
||||
return values.join("\n");
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::giveEssentialItem(StringList const& arguments) {
|
||||
String ClientCommandProcessor::giveEssentialItem(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -354,7 +370,8 @@ String ClientCommandProcessor::giveEssentialItem(StringList const& arguments) {
|
||||
}
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::makeTechAvailable(StringList const& arguments) {
|
||||
String ClientCommandProcessor::makeTechAvailable(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -365,7 +382,8 @@ String ClientCommandProcessor::makeTechAvailable(StringList const& arguments) {
|
||||
return strf("Added {} to player's visible techs", arguments.at(0));
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::enableTech(StringList const& arguments) {
|
||||
String ClientCommandProcessor::enableTech(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
@ -377,7 +395,8 @@ String ClientCommandProcessor::enableTech(StringList const& arguments) {
|
||||
return strf("Player tech {} enabled", arguments.at(0));
|
||||
}
|
||||
|
||||
String ClientCommandProcessor::upgradeShip(StringList const& arguments) {
|
||||
String ClientCommandProcessor::upgradeShip(String const& argumentsString) {
|
||||
auto arguments = m_parser.tokenizeToStringList(argumentsString);
|
||||
if (!adminCommandAllowed())
|
||||
return "You must be an admin to use this command.";
|
||||
|
||||
|
@ -29,40 +29,40 @@ private:
|
||||
String reload();
|
||||
String whoami();
|
||||
String gravity();
|
||||
String debug(StringList const& arguments);
|
||||
String debug(String const& argumentsString);
|
||||
String boxes();
|
||||
String fullbright();
|
||||
String asyncLighting();
|
||||
String setGravity(StringList const& arguments);
|
||||
String setGravity(String const& argumentsString);
|
||||
String resetGravity();
|
||||
String fixedCamera();
|
||||
String monochromeLighting();
|
||||
String radioMessage(StringList const& arguments);
|
||||
String radioMessage(String const& argumentsString);
|
||||
String clearRadioMessages();
|
||||
String clearCinematics();
|
||||
String startQuest(StringList const& arguments);
|
||||
String completeQuest(StringList const& arguments);
|
||||
String failQuest(StringList const& arguments);
|
||||
String previewNewQuest(StringList const& arguments);
|
||||
String previewQuestComplete(StringList const& arguments);
|
||||
String previewQuestFailed(StringList const& arguments);
|
||||
String startQuest(String const& argumentsString);
|
||||
String completeQuest(String const& argumentsString);
|
||||
String failQuest(String const& argumentsString);
|
||||
String previewNewQuest(String const& argumentsString);
|
||||
String previewQuestComplete(String const& argumentsString);
|
||||
String previewQuestFailed(String const& argumentsString);
|
||||
String clearScannedObjects();
|
||||
String playTime();
|
||||
String deathCount();
|
||||
String cinema(StringList const& arguments);
|
||||
String cinema(String const& argumentsString);
|
||||
String suicide();
|
||||
String naked();
|
||||
String resetAchievements();
|
||||
String statistic(StringList const& arguments);
|
||||
String giveEssentialItem(StringList const& arguments);
|
||||
String makeTechAvailable(StringList const& arguments);
|
||||
String enableTech(StringList const& arguments);
|
||||
String upgradeShip(StringList const& arguments);
|
||||
String statistic(String const& argumentsString);
|
||||
String giveEssentialItem(String const& argumentsString);
|
||||
String makeTechAvailable(String const& argumentsString);
|
||||
String enableTech(String const& argumentsString);
|
||||
String upgradeShip(String const& argumentsString);
|
||||
|
||||
UniverseClientPtr m_universeClient;
|
||||
CinematicPtr m_cinematicOverlay;
|
||||
MainInterfacePaneManager* m_paneManager;
|
||||
CaseInsensitiveStringMap<function<String(StringList const&)>> m_builtinCommands;
|
||||
CaseInsensitiveStringMap<function<String(String const&)>> m_builtinCommands;
|
||||
StringMap<StringList> m_macroCommands;
|
||||
ShellParser m_parser;
|
||||
LuaBaseComponent m_scriptComponent;
|
||||
|
@ -27,6 +27,11 @@ LuaCallbacks LuaBindings::makeInterfaceCallbacks(MainInterface* mainInterface) {
|
||||
return GuiContext::singleton().interfaceScale();
|
||||
});
|
||||
|
||||
callbacks.registerCallback("queueMessage", [mainInterface](String const& message, Maybe<float> cooldown, Maybe<float> springState) {
|
||||
mainInterface->queueMessage(message, cooldown, springState.value(0));
|
||||
});
|
||||
|
||||
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
|
@ -62,8 +62,8 @@ namespace Star {
|
||||
|
||||
GuiMessage::GuiMessage() : message(), cooldown(), springState() {}
|
||||
|
||||
GuiMessage::GuiMessage(String const& message, float cooldown)
|
||||
: message(message), cooldown(cooldown), springState(0) {}
|
||||
GuiMessage::GuiMessage(String const& message, float cooldown, float spring)
|
||||
: message(message), cooldown(cooldown), springState(spring) {}
|
||||
|
||||
MainInterface::MainInterface(UniverseClientPtr client, WorldPainterPtr painter, CinematicPtr cinematicOverlay) {
|
||||
m_state = Running;
|
||||
@ -369,6 +369,9 @@ bool MainInterface::handleInputEvent(InputEvent const& event) {
|
||||
player->endTrigger();
|
||||
}
|
||||
|
||||
for (auto& pair : m_canvases)
|
||||
pair.second->sendEvent(event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -863,11 +866,15 @@ void MainInterface::doChat(String const& chat, bool addToHistory) {
|
||||
m_chat->addHistory(chat);
|
||||
}
|
||||
|
||||
void MainInterface::queueMessage(String const& message) {
|
||||
auto guiMessage = make_shared<GuiMessage>(message, m_config->messageTime);
|
||||
void MainInterface::queueMessage(String const& message, Maybe<float> cooldown, float spring) {
|
||||
auto guiMessage = make_shared<GuiMessage>(message, cooldown.value(m_config->messageTime), spring);
|
||||
m_messages.append(guiMessage);
|
||||
}
|
||||
|
||||
void MainInterface::queueMessage(String const& message) {
|
||||
queueMessage(message, m_config->messageTime, 0.0f);
|
||||
}
|
||||
|
||||
void MainInterface::queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> request)
|
||||
{
|
||||
m_queuedJoinRequests.push_back(request);
|
||||
@ -927,19 +934,22 @@ void MainInterface::warpTo(WarpAction const& warpAction) {
|
||||
}
|
||||
|
||||
CanvasWidgetPtr MainInterface::fetchCanvas(String const& canvasName, bool ignoreInterfaceScale) {
|
||||
CanvasWidgetPtr canvas;
|
||||
|
||||
if (auto canvasPtr = m_canvases.ptr(canvasName))
|
||||
return *canvasPtr;
|
||||
canvas = *canvasPtr;
|
||||
else {
|
||||
CanvasWidgetPtr canvas = m_canvases.emplace(canvasName, make_shared<CanvasWidget>()).first->second;
|
||||
m_canvases.emplace(canvasName, canvas = make_shared<CanvasWidget>());
|
||||
canvas->setPosition(Vec2I());
|
||||
if (ignoreInterfaceScale)
|
||||
canvas->setSize(Vec2I(m_guiContext->windowSize()));
|
||||
else
|
||||
canvas->setSize(Vec2I(m_guiContext->windowInterfaceSize()));
|
||||
}
|
||||
|
||||
canvas->setIgnoreInterfaceScale(ignoreInterfaceScale);
|
||||
return canvas;
|
||||
}
|
||||
}
|
||||
|
||||
PanePtr MainInterface::createEscapeDialog() {
|
||||
auto assets = Root::singleton().assets();
|
||||
|
@ -49,7 +49,7 @@ STAR_CLASS(MainInterface);
|
||||
|
||||
struct GuiMessage {
|
||||
GuiMessage();
|
||||
GuiMessage(String const& message, float cooldown);
|
||||
GuiMessage(String const& message, float cooldown, float spring = 0);
|
||||
|
||||
String message;
|
||||
float cooldown;
|
||||
@ -105,7 +105,9 @@ public:
|
||||
|
||||
void doChat(String const& chat, bool addToHistory);
|
||||
|
||||
void queueMessage(String const& message, Maybe<float> cooldown, float spring);
|
||||
void queueMessage(String const& message);
|
||||
|
||||
void queueItemPickupText(ItemPtr const& item);
|
||||
void queueJoinRequest(pair<String, RpcPromiseKeeper<P2PJoinRequestReply>> request);
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarWorldClient.hpp"
|
||||
#include "StarWorldPainter.hpp"
|
||||
#include "StarVoice.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
@ -80,7 +81,7 @@ void MainMixer::update(bool muteSfx, bool muteMusic) {
|
||||
auto cameraPos = m_worldPainter->camera().centerWorldPosition();
|
||||
auto worldGeometry = currentWorld->geometry();
|
||||
|
||||
m_mixer->update([&](unsigned channel, Vec2F pos, float rangeMultiplier) {
|
||||
Mixer::PositionalAttenuationFunction attenuationFunction = [&](unsigned channel, Vec2F pos, float rangeMultiplier) {
|
||||
Vec2F playerDiff = worldGeometry.diff(pos, playerPos);
|
||||
Vec2F cameraDiff = worldGeometry.diff(pos, cameraPos);
|
||||
float playerMagSq = playerDiff.magnitudeSquared();
|
||||
@ -107,7 +108,12 @@ void MainMixer::update(bool muteSfx, bool muteMusic) {
|
||||
float maxDistance = baseMaxDistance * rangeMultiplier * lerp((stereoIncidence + 1.0f) / 2.0f, stereoAdjustmentRange[0], stereoAdjustmentRange[1]);
|
||||
|
||||
return pow(clamp(diffMagnitude / maxDistance, 0.0f, 1.0f), 1.0f / attenuationGamma);
|
||||
});
|
||||
};
|
||||
|
||||
if (Voice* voice = Voice::singletonPtr())
|
||||
voice->update(attenuationFunction);
|
||||
|
||||
m_mixer->update(attenuationFunction);
|
||||
|
||||
} else {
|
||||
if (m_mixer->hasEffect("lowpass"))
|
||||
@ -115,6 +121,9 @@ void MainMixer::update(bool muteSfx, bool muteMusic) {
|
||||
if (m_mixer->hasEffect("echo"))
|
||||
m_mixer->removeEffect("echo", 0);
|
||||
|
||||
if (Voice* voice = Voice::singletonPtr())
|
||||
voice->update();
|
||||
|
||||
m_mixer->update();
|
||||
}
|
||||
}
|
||||
@ -127,8 +136,8 @@ void MainMixer::setVolume(float volume, float rampTime) {
|
||||
m_mixer->setVolume(volume, rampTime);
|
||||
}
|
||||
|
||||
void MainMixer::read(int16_t* sampleData, size_t frameCount) {
|
||||
m_mixer->read(sampleData, frameCount);
|
||||
void MainMixer::read(int16_t* sampleData, size_t frameCount, Mixer::ExtraMixFunction extraMixFunction) {
|
||||
m_mixer->read(sampleData, frameCount, extraMixFunction);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ public:
|
||||
MixerPtr mixer() const;
|
||||
|
||||
void setVolume(float volume, float rampTime = 0.0f);
|
||||
void read(int16_t* sampleData, size_t frameCount);
|
||||
void read(int16_t* sampleData, size_t frameCount, Mixer::ExtraMixFunction = {});
|
||||
|
||||
private:
|
||||
UniverseClientPtr m_universeClient;
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "StarLabelWidget.hpp"
|
||||
#include "StarAssets.hpp"
|
||||
#include "StarKeybindingsMenu.hpp"
|
||||
#include "StarVoiceSettingsMenu.hpp"
|
||||
#include "StarBindingsMenu.hpp"
|
||||
#include "StarGraphicsMenu.hpp"
|
||||
|
||||
@ -49,6 +50,12 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
|
||||
reader.registerCallback("showKeybindings", [=](Widget*) {
|
||||
displayControls();
|
||||
});
|
||||
reader.registerCallback("showVoiceSettings", [=](Widget*) {
|
||||
displayVoiceSettings();
|
||||
});
|
||||
reader.registerCallback("showVoicePlayers", [=](Widget*) {
|
||||
|
||||
});
|
||||
reader.registerCallback("showModBindings", [=](Widget*) {
|
||||
displayModBindings();
|
||||
});
|
||||
@ -74,6 +81,7 @@ OptionsMenu::OptionsMenu(PaneManager* manager)
|
||||
m_sfxSlider->setRange(m_sfxRange, assets->json("/interface/optionsmenu/optionsmenu.config:sfxDelta").toInt());
|
||||
m_musicSlider->setRange(m_musicRange, assets->json("/interface/optionsmenu/optionsmenu.config:musicDelta").toInt());
|
||||
|
||||
m_voiceSettingsMenu = make_shared<VoiceSettingsMenu>(assets->json(config.getString("voiceSettingsPanePath", "/interface/opensb/voicechat/voicechat.config")));
|
||||
m_modBindingsMenu = make_shared<BindingsMenu>(assets->json(config.getString("bindingsPanePath", "/interface/opensb/bindings/bindings.config")));
|
||||
m_keybindingsMenu = make_shared<KeybindingsMenu>();
|
||||
m_graphicsMenu = make_shared<GraphicsMenu>();
|
||||
@ -169,6 +177,10 @@ void OptionsMenu::displayControls() {
|
||||
m_paneManager->displayPane(PaneLayer::ModalWindow, m_keybindingsMenu);
|
||||
}
|
||||
|
||||
void OptionsMenu::displayVoiceSettings() {
|
||||
m_paneManager->displayPane(PaneLayer::ModalWindow, m_voiceSettingsMenu);
|
||||
}
|
||||
|
||||
void OptionsMenu::displayModBindings() {
|
||||
m_paneManager->displayPane(PaneLayer::ModalWindow, m_modBindingsMenu);
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace Star {
|
||||
STAR_CLASS(SliderBarWidget);
|
||||
STAR_CLASS(ButtonWidget);
|
||||
STAR_CLASS(LabelWidget);
|
||||
STAR_CLASS(VoiceSettingsMenu);
|
||||
STAR_CLASS(KeybindingsMenu);
|
||||
STAR_CLASS(GraphicsMenu);
|
||||
STAR_CLASS(BindingsMenu);
|
||||
@ -38,6 +39,7 @@ private:
|
||||
void syncGuiToConf();
|
||||
|
||||
void displayControls();
|
||||
void displayVoiceSettings();
|
||||
void displayModBindings();
|
||||
void displayGraphics();
|
||||
|
||||
@ -59,6 +61,7 @@ private:
|
||||
JsonObject m_origConfig;
|
||||
JsonObject m_localChanges;
|
||||
|
||||
VoiceSettingsMenuPtr m_voiceSettingsMenu;
|
||||
BindingsMenuPtr m_modBindingsMenu;
|
||||
KeybindingsMenuPtr m_keybindingsMenu;
|
||||
GraphicsMenuPtr m_graphicsMenu;
|
||||
|
716
source/frontend/StarVoice.cpp
Normal file
@ -0,0 +1,716 @@
|
||||
#include "StarVoice.hpp"
|
||||
#include "StarFormat.hpp"
|
||||
#include "StarJsonExtra.hpp"
|
||||
#include "StarApplicationController.hpp"
|
||||
#include "StarTime.hpp"
|
||||
#include "StarRoot.hpp"
|
||||
#include "StarLogging.hpp"
|
||||
#include "StarInterpolation.hpp"
|
||||
#include "opus/include/opus.h"
|
||||
|
||||
#include "SDL.h"
|
||||
|
||||
constexpr int VOICE_SAMPLE_RATE = 48000;
|
||||
constexpr int VOICE_FRAME_SIZE = 960;
|
||||
|
||||
constexpr int VOICE_MAX_FRAME_SIZE = 6 * VOICE_FRAME_SIZE;
|
||||
constexpr int VOICE_MAX_PACKET_SIZE = 3 * 1276;
|
||||
|
||||
constexpr uint16_t VOICE_VERSION = 1;
|
||||
|
||||
namespace Star {
|
||||
|
||||
EnumMap<VoiceInputMode> const VoiceInputModeNames{
|
||||
{VoiceInputMode::VoiceActivity, "VoiceActivity"},
|
||||
{VoiceInputMode::PushToTalk, "PushToTalk"}
|
||||
};
|
||||
|
||||
EnumMap<VoiceChannelMode> const VoiceChannelModeNames{
|
||||
{VoiceChannelMode::Mono, "Mono"},
|
||||
{VoiceChannelMode::Stereo, "Stereo"}
|
||||
};
|
||||
|
||||
inline float getAudioChunkLoudness(int16_t* data, size_t samples, float volume) {
|
||||
if (!samples)
|
||||
return 0.f;
|
||||
|
||||
double rms = 0.;
|
||||
for (size_t i = 0; i != samples; ++i) {
|
||||
float sample = ((float)data[i] / 32767.f) * volume;
|
||||
rms += (double)(sample * sample);
|
||||
}
|
||||
|
||||
float fRms = sqrtf((float)(rms / samples));
|
||||
|
||||
if (fRms > 0)
|
||||
return std::clamp<float>(20.f * log10f(fRms), -127.f, 0.f);
|
||||
else
|
||||
return -127.f;
|
||||
}
|
||||
|
||||
float getAudioLoudness(int16_t* data, size_t samples, float volume = 1.0f) {
|
||||
constexpr size_t CHUNK_SIZE = 50;
|
||||
|
||||
float highest = -127.f;
|
||||
for (size_t i = 0; i < samples; i += CHUNK_SIZE) {
|
||||
float level = getAudioChunkLoudness(data + i, std::min<size_t>(i + CHUNK_SIZE, samples) - i, volume);
|
||||
if (level > highest)
|
||||
highest = level;
|
||||
}
|
||||
|
||||
return highest;
|
||||
}
|
||||
|
||||
struct VoiceAudioStream {
|
||||
// TODO: This should really be a ring buffer instead.
|
||||
std::queue<int16_t> samples;
|
||||
SDL_AudioStream* sdlAudioStreamMono;
|
||||
SDL_AudioStream* sdlAudioStreamStereo;
|
||||
Mutex mutex;
|
||||
|
||||
VoiceAudioStream()
|
||||
: sdlAudioStreamMono (SDL_NewAudioStream(AUDIO_S16, 1, 48000, AUDIO_S16SYS, 1, 44100))
|
||||
, sdlAudioStreamStereo(SDL_NewAudioStream(AUDIO_S16, 2, 48000, AUDIO_S16SYS, 2, 44100)) {};
|
||||
~VoiceAudioStream() {
|
||||
SDL_FreeAudioStream(sdlAudioStreamMono);
|
||||
SDL_FreeAudioStream(sdlAudioStreamStereo);
|
||||
}
|
||||
|
||||
inline int16_t take() {
|
||||
int16_t sample = 0;
|
||||
if (!samples.empty()) {
|
||||
sample = samples.front();
|
||||
samples.pop();
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
|
||||
size_t resample(int16_t* in, size_t inSamples, std::vector<int16_t>& out, bool mono) {
|
||||
SDL_AudioStream* stream = mono ? sdlAudioStreamMono : sdlAudioStreamStereo;
|
||||
SDL_AudioStreamPut(stream, in, inSamples * sizeof(int16_t));
|
||||
if (int available = SDL_AudioStreamAvailable(stream)) {
|
||||
out.resize(available / 2);
|
||||
SDL_AudioStreamGet(stream, out.data(), available);
|
||||
return available;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
Voice::Speaker::Speaker(SpeakerId id)
|
||||
: decoderMono (createDecoder(1), opus_decoder_destroy)
|
||||
, decoderStereo(createDecoder(2), opus_decoder_destroy) {
|
||||
speakerId = id;
|
||||
audioStream = make_shared<VoiceAudioStream>();
|
||||
}
|
||||
|
||||
Json Voice::Speaker::toJson() const {
|
||||
return JsonObject{
|
||||
{"speakerId", speakerId},
|
||||
{"entityId", entityId },
|
||||
{"name", name },
|
||||
{"playing", (bool)playing},
|
||||
{"muted", (bool)muted },
|
||||
{"decibels", (float)decibelLevel},
|
||||
{"smoothDecibels", (float)smoothDb },
|
||||
{"position", jsonFromVec2F(position)}
|
||||
};
|
||||
}
|
||||
|
||||
Voice* Voice::s_singleton;
|
||||
|
||||
Voice* Voice::singletonPtr() {
|
||||
return s_singleton;
|
||||
}
|
||||
|
||||
Voice& Voice::singleton() {
|
||||
if (!s_singleton)
|
||||
throw VoiceException("Voice::singleton() called with no Voice instance available");
|
||||
else
|
||||
return *s_singleton;
|
||||
}
|
||||
|
||||
Voice::Voice(ApplicationControllerPtr appController) : m_encoder(nullptr, opus_encoder_destroy) {
|
||||
if (s_singleton)
|
||||
throw VoiceException("Singleton Voice has been constructed twice");
|
||||
|
||||
m_clientSpeaker = make_shared<Speaker>(m_speakerId);
|
||||
m_inputMode = VoiceInputMode::PushToTalk;
|
||||
m_channelMode = VoiceChannelMode::Mono;
|
||||
m_applicationController = appController;
|
||||
|
||||
m_stopThread = false;
|
||||
m_thread = Thread::invoke("Voice::thread", mem_fn(&Voice::thread), this);
|
||||
|
||||
s_singleton = this;
|
||||
}
|
||||
|
||||
Voice::~Voice() {
|
||||
m_stopThread = true;
|
||||
|
||||
{
|
||||
MutexLocker locker(m_threadMutex);
|
||||
m_threadCond.broadcast();
|
||||
}
|
||||
|
||||
m_thread.finish();
|
||||
|
||||
if (m_nextSaveTime)
|
||||
save();
|
||||
|
||||
closeDevice();
|
||||
|
||||
s_singleton = nullptr;
|
||||
}
|
||||
|
||||
void Voice::init() {
|
||||
resetEncoder();
|
||||
resetDevice();
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline bool change(T& value, T newValue, bool& out) {
|
||||
bool changed = value != newValue;
|
||||
out |= changed;
|
||||
value = move(newValue);
|
||||
return changed;
|
||||
}
|
||||
|
||||
void Voice::loadJson(Json const& config, bool skipSave) {
|
||||
// Not all keys are required
|
||||
|
||||
bool changed = false;
|
||||
|
||||
{
|
||||
bool enabled = shouldEnableInput();
|
||||
m_enabled = config.getBool("enabled", m_enabled);
|
||||
m_inputEnabled = config.getBool("inputEnabled", m_inputEnabled);
|
||||
if (shouldEnableInput() != enabled) {
|
||||
changed = true;
|
||||
resetDevice();
|
||||
}
|
||||
}
|
||||
|
||||
if (config.contains("deviceName") // Make sure null-type key exists
|
||||
&& change(m_deviceName, config.optString("deviceName"), changed))
|
||||
resetDevice();
|
||||
|
||||
m_threshold = config.getFloat("threshold", m_threshold);
|
||||
m_inputVolume = config.getFloat("inputVolume", m_inputVolume);
|
||||
m_outputVolume = config.getFloat("outputVolume", m_outputVolume);
|
||||
|
||||
if (change(m_loopback, config.getBool("loopback", m_loopback), changed))
|
||||
m_clientSpeaker->playing = false;
|
||||
|
||||
if (auto inputMode = config.optString("inputMode")) {
|
||||
if (change(m_inputMode, VoiceInputModeNames.getLeft(*inputMode), changed))
|
||||
m_lastInputTime = 0;
|
||||
}
|
||||
|
||||
if (auto channelMode = config.optString("channelMode")) {
|
||||
if (change(m_channelMode, VoiceChannelModeNames.getLeft(*channelMode), changed)) {
|
||||
closeDevice();
|
||||
resetEncoder();
|
||||
resetDevice();
|
||||
}
|
||||
}
|
||||
|
||||
if (changed && !skipSave)
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Json Voice::saveJson() const {
|
||||
return JsonObject{
|
||||
{"enabled", m_enabled},
|
||||
{"inputEnabled", m_inputEnabled},
|
||||
{"inputDevice", m_deviceName ? *m_deviceName : Json()},
|
||||
{"threshold", m_threshold},
|
||||
{"inputVolume", m_inputVolume},
|
||||
{"outputVolume", m_outputVolume},
|
||||
{"inputMode", VoiceInputModeNames.getRight(m_inputMode)},
|
||||
{"channelMode", VoiceChannelModeNames.getRight(m_channelMode)},
|
||||
{"loopback", m_loopback},
|
||||
{"version", 1}
|
||||
};
|
||||
}
|
||||
|
||||
void Voice::save() const {
|
||||
if (Root* root = Root::singletonPtr()) {
|
||||
if (auto config = root->configuration())
|
||||
config->set("voice", saveJson());
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::scheduleSave() {
|
||||
if (!m_nextSaveTime)
|
||||
m_nextSaveTime = Time::monotonicMilliseconds() + 2000;
|
||||
}
|
||||
|
||||
Voice::SpeakerPtr Voice::setLocalSpeaker(SpeakerId speakerId) {
|
||||
if (m_speakers.contains(m_speakerId))
|
||||
m_speakers.remove(m_speakerId);
|
||||
|
||||
m_clientSpeaker->speakerId = m_speakerId = speakerId;
|
||||
return m_speakers.insert(m_speakerId, m_clientSpeaker).first->second;
|
||||
}
|
||||
|
||||
Voice::SpeakerPtr Voice::localSpeaker() {
|
||||
return m_clientSpeaker;
|
||||
}
|
||||
|
||||
Voice::SpeakerPtr Voice::speaker(SpeakerId speakerId) {
|
||||
if (m_speakerId == speakerId)
|
||||
return m_clientSpeaker;
|
||||
else {
|
||||
if (SpeakerPtr const* ptr = m_speakers.ptr(speakerId))
|
||||
return *ptr;
|
||||
else
|
||||
return m_speakers.emplace(speakerId, make_shared<Speaker>(speakerId)).first->second;
|
||||
}
|
||||
}
|
||||
|
||||
HashMap<Voice::SpeakerId, Voice::SpeakerPtr>& Voice::speakers() {
|
||||
return m_speakers;
|
||||
}
|
||||
|
||||
List<Voice::SpeakerPtr> Voice::sortedSpeakers(bool onlyPlaying) {
|
||||
List<SpeakerPtr> result;
|
||||
|
||||
auto sorter = [](SpeakerPtr const& a, SpeakerPtr const& b) -> bool {
|
||||
if (a->lastPlayTime != b->lastPlayTime)
|
||||
return a->lastPlayTime < b->lastPlayTime;
|
||||
else
|
||||
return a->speakerId < b->speakerId;
|
||||
};
|
||||
|
||||
for (auto& p : m_speakers) {
|
||||
if (!onlyPlaying || p.second->playing)
|
||||
result.insertSorted(p.second, sorter);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Voice::clearSpeakers() {
|
||||
auto it = m_speakers.begin();
|
||||
while (it != m_speakers.end()) {
|
||||
if (it->second == m_clientSpeaker)
|
||||
it = ++it;
|
||||
else
|
||||
it = m_speakers.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::readAudioData(uint8_t* stream, int len) {
|
||||
auto now = Time::monotonicMilliseconds();
|
||||
bool active = m_encoder && m_encodedChunksLength < 2048
|
||||
&& (m_inputMode == VoiceInputMode::VoiceActivity || now < m_lastInputTime);
|
||||
|
||||
size_t sampleCount = len / 2;
|
||||
|
||||
if (active) {
|
||||
float decibels = getAudioLoudness((int16_t*)stream, sampleCount);
|
||||
|
||||
if (m_inputMode == VoiceInputMode::VoiceActivity) {
|
||||
if (decibels > m_threshold)
|
||||
m_lastThresholdTime = now;
|
||||
active = now - m_lastThresholdTime < 50;
|
||||
}
|
||||
}
|
||||
|
||||
m_clientSpeaker->decibelLevel = getAudioLoudness((int16_t*)stream, sampleCount, m_inputVolume);
|
||||
|
||||
if (!m_loopback) {
|
||||
if (active && !m_clientSpeaker->playing)
|
||||
m_clientSpeaker->lastPlayTime = now;
|
||||
|
||||
m_clientSpeaker->playing = active;
|
||||
}
|
||||
|
||||
MutexLocker captureLock(m_captureMutex);
|
||||
if (active) {
|
||||
m_capturedChunksFrames += sampleCount / m_deviceChannels;
|
||||
auto data = (opus_int16*)malloc(len);
|
||||
memcpy(data, stream, len);
|
||||
m_capturedChunks.emplace(data, sampleCount); // takes ownership
|
||||
m_threadCond.signal();
|
||||
}
|
||||
else { // Clear out any residual data so they don't manifest at the start of the next encode, whenever that is
|
||||
while (!m_capturedChunks.empty())
|
||||
m_capturedChunks.pop();
|
||||
|
||||
m_capturedChunksFrames = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::mix(int16_t* buffer, size_t frameCount, unsigned channels) {
|
||||
size_t samples = frameCount * channels;
|
||||
static std::vector<int16_t> finalBuffer, speakerBuffer;
|
||||
static std::vector<int32_t> sharedBuffer; //int32 to reduce clipping
|
||||
speakerBuffer.resize(samples);
|
||||
sharedBuffer.resize(samples);
|
||||
|
||||
bool mix = false;
|
||||
{
|
||||
MutexLocker lock(m_activeSpeakersMutex);
|
||||
auto it = m_activeSpeakers.begin();
|
||||
while (it != m_activeSpeakers.end()) {
|
||||
SpeakerPtr const& speaker = *it;
|
||||
VoiceAudioStream* audio = speaker->audioStream.get();
|
||||
MutexLocker audioLock(audio->mutex);
|
||||
if (speaker->playing && !audio->samples.empty()) {
|
||||
for (size_t i = 0; i != samples; ++i)
|
||||
speakerBuffer[i] = audio->take();
|
||||
|
||||
if (speaker != m_clientSpeaker)
|
||||
speaker->decibelLevel = getAudioLoudness(speakerBuffer.data(), samples);
|
||||
|
||||
if (!speaker->muted) {
|
||||
mix = true;
|
||||
|
||||
float volume = speaker->volume;
|
||||
Array2F levels = speaker->channelVolumes;
|
||||
for (size_t i = 0; i != samples; ++i)
|
||||
sharedBuffer[i] += (int32_t)(speakerBuffer[i]) * levels[i % 2] * volume;
|
||||
//Blends the weaker channel into the stronger one,
|
||||
/* unused, is a bit too strong on stereo music.
|
||||
float maxLevel = max(levels[0], levels[1]);
|
||||
float leftToRight = maxLevel != 0.0f ? 1.0f - (levels[0] / maxLevel) : 0.0f;
|
||||
float rightToLeft = maxLevel != 0.0f ? 1.0f - (levels[1] / maxLevel) : 0.0f;
|
||||
|
||||
int16_t* speakerData = speakerBuffer.data();
|
||||
int32_t* sharedData = sharedBuffer.data();
|
||||
for (size_t i = 0; i != frameCount; ++i) {
|
||||
auto leftSample = (float)*speakerData++;
|
||||
auto rightSample = (float)*speakerData++;
|
||||
|
||||
if (rightToLeft != 0.0f)
|
||||
leftSample = ( leftSample + rightSample * rightToLeft) / (1.0f + rightToLeft);
|
||||
if (leftToRight != 0.0f)
|
||||
rightSample = (rightSample + leftSample * leftToRight) / (1.0f + leftToRight);
|
||||
|
||||
*sharedData++ += (int32_t)leftSample * levels[0];
|
||||
*sharedData++ += (int32_t)rightSample * levels[1];
|
||||
}
|
||||
//*/
|
||||
}
|
||||
++it;
|
||||
}
|
||||
else {
|
||||
speaker->playing = false;
|
||||
if (speaker != m_clientSpeaker)
|
||||
speaker->decibelLevel = -96.0f;
|
||||
it = m_activeSpeakers.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mix) {
|
||||
finalBuffer.resize(sharedBuffer.size(), 0);
|
||||
|
||||
float vol = m_outputVolume;
|
||||
for (size_t i = 0; i != sharedBuffer.size(); ++i)
|
||||
finalBuffer[i] = (int16_t)clamp<int>(sharedBuffer[i] * vol, INT16_MIN, INT16_MAX);
|
||||
|
||||
SDL_MixAudioFormat((Uint8*)buffer, (Uint8*)finalBuffer.data(), AUDIO_S16, finalBuffer.size() * sizeof(int16_t), SDL_MIX_MAXVOLUME);
|
||||
memset(sharedBuffer.data(), 0, sharedBuffer.size() * sizeof(int32_t));
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::update(PositionalAttenuationFunction positionalAttenuationFunction) {
|
||||
for (auto& entry : m_speakers) {
|
||||
if (SpeakerPtr& speaker = entry.second) {
|
||||
if (positionalAttenuationFunction) {
|
||||
speaker->channelVolumes = {
|
||||
1.0f - positionalAttenuationFunction(0, speaker->position, 1.0f),
|
||||
1.0f - positionalAttenuationFunction(1, speaker->position, 1.0f)
|
||||
};
|
||||
}
|
||||
else
|
||||
speaker->channelVolumes = Vec2F::filled(1.0f);
|
||||
|
||||
auto& dbHistory = speaker->dbHistory;
|
||||
memcpy(&dbHistory[1], &dbHistory[0], (dbHistory.size() - 1) * sizeof(float));
|
||||
dbHistory[0] = speaker->decibelLevel;
|
||||
float smoothDb = 0.0f;
|
||||
for (float dB : dbHistory)
|
||||
smoothDb += dB;
|
||||
|
||||
speaker->smoothDb = smoothDb / dbHistory.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_nextSaveTime && Time::monotonicMilliseconds() > m_nextSaveTime) {
|
||||
m_nextSaveTime = 0;
|
||||
save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void Voice::setDeviceName(Maybe<String> deviceName) {
|
||||
if (m_deviceName == deviceName)
|
||||
return;
|
||||
|
||||
m_deviceName = deviceName;
|
||||
if (m_deviceOpen)
|
||||
openDevice();
|
||||
}
|
||||
|
||||
StringList Voice::availableDevices() {
|
||||
int devices = SDL_GetNumAudioDevices(1);
|
||||
StringList deviceList;
|
||||
if (devices > 0) {
|
||||
deviceList.reserve(devices);
|
||||
for (size_t i = 0; i != devices; ++i)
|
||||
deviceList.emplace_back(SDL_GetAudioDeviceName(i, 1));
|
||||
}
|
||||
deviceList.sort();
|
||||
return deviceList;
|
||||
}
|
||||
|
||||
int Voice::send(DataStreamBuffer& out, size_t budget) {
|
||||
out.setByteOrder(ByteOrder::LittleEndian);
|
||||
out.write<uint16_t>(VOICE_VERSION);
|
||||
|
||||
MutexLocker encodeLock(m_encodeMutex);
|
||||
|
||||
if (m_encodedChunks.empty())
|
||||
return 0;
|
||||
|
||||
std::vector<ByteArray> encodedChunks = move(m_encodedChunks);
|
||||
size_t encodedChunksLength = m_encodedChunksLength;
|
||||
m_encodedChunksLength = 0;
|
||||
|
||||
encodeLock.unlock();
|
||||
|
||||
for (auto& chunk : encodedChunks) {
|
||||
out.write<uint32_t>(chunk.size());
|
||||
out.writeBytes(chunk);
|
||||
if (budget && (budget -= min<size_t>(budget, chunk.size())) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
m_lastSentTime = Time::monotonicMilliseconds();
|
||||
if (m_loopback)
|
||||
receive(m_clientSpeaker, { out.ptr(), out.size() });
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool Voice::receive(SpeakerPtr speaker, std::string_view view) {
|
||||
if (!m_enabled || !speaker || view.empty())
|
||||
return false;
|
||||
|
||||
try {
|
||||
DataStreamExternalBuffer reader(view.data(), view.size());
|
||||
reader.setByteOrder(ByteOrder::LittleEndian);
|
||||
|
||||
if (reader.read<uint16_t>() > VOICE_VERSION)
|
||||
return false;
|
||||
|
||||
uint32_t opusLength = 0;
|
||||
while (!reader.atEnd()) {
|
||||
reader >> opusLength;
|
||||
if (reader.pos() + opusLength > reader.size())
|
||||
throw VoiceException("Opus packet length goes past end of buffer"s, false);
|
||||
auto opusData = (unsigned char*)reader.ptr() + reader.pos();
|
||||
reader.seek(opusLength, IOSeek::Relative);
|
||||
|
||||
int channels = opus_packet_get_nb_channels(opusData);
|
||||
if (channels == OPUS_INVALID_PACKET)
|
||||
continue;
|
||||
|
||||
bool mono = channels == 1;
|
||||
OpusDecoder* decoder = mono ? speaker->decoderMono.get() : speaker->decoderStereo.get();
|
||||
int samples = opus_decoder_get_nb_samples(decoder, opusData, opusLength);
|
||||
if (samples < 0)
|
||||
throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false);
|
||||
|
||||
m_decodeBuffer.resize(samples * (size_t)channels);
|
||||
|
||||
int decodedSamples = opus_decode(decoder, opusData, opusLength, m_decodeBuffer.data(), m_decodeBuffer.size() * sizeof(int16_t), 0);
|
||||
if (decodedSamples <= 0) {
|
||||
if (decodedSamples < 0)
|
||||
throw VoiceException(strf("Decoder error: {}", opus_strerror(samples)), false);
|
||||
return true;
|
||||
}
|
||||
|
||||
//Logger::info("Voice: decoded Opus chunk {} bytes -> {} samples", opusLength, decodedSamples * channels);
|
||||
|
||||
speaker->audioStream->resample(m_decodeBuffer.data(), (size_t)decodedSamples * channels, m_resampleBuffer, mono);
|
||||
|
||||
{
|
||||
MutexLocker lock(speaker->audioStream->mutex);
|
||||
auto& samples = speaker->audioStream->samples;
|
||||
|
||||
auto now = Time::monotonicMilliseconds();
|
||||
if (now - speaker->lastReceiveTime < 1000) {
|
||||
auto limit = (size_t)speaker->minimumPlaySamples + 22050;
|
||||
if (samples.size() > limit) { // skip ahead if we're getting too far
|
||||
for (size_t i = samples.size(); i >= limit; --i)
|
||||
samples.pop();
|
||||
}
|
||||
}
|
||||
else
|
||||
samples = std::queue<int16_t>();
|
||||
|
||||
speaker->lastReceiveTime = now;
|
||||
|
||||
if (mono) {
|
||||
for (int16_t sample : m_resampleBuffer) {
|
||||
samples.push(sample);
|
||||
samples.push(sample);
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (int16_t sample : m_resampleBuffer)
|
||||
samples.push(sample);
|
||||
}
|
||||
}
|
||||
playSpeaker(speaker, channels);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
catch (StarException const& e) {
|
||||
Logger::error("Voice: Error receiving voice data for speaker #{} ('{}'): {}", speaker->speakerId, speaker->name, e.what());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Voice::setInput(bool input) {
|
||||
m_lastInputTime = (m_deviceOpen && input) ? Time::monotonicMilliseconds() + 1000 : 0;
|
||||
}
|
||||
|
||||
OpusDecoder* Voice::createDecoder(int channels) {
|
||||
int error;
|
||||
OpusDecoder* decoder = opus_decoder_create(VOICE_SAMPLE_RATE, channels, &error);
|
||||
if (error != OPUS_OK)
|
||||
throw VoiceException::format("Could not create decoder: {}", opus_strerror(error));
|
||||
else
|
||||
return decoder;
|
||||
}
|
||||
|
||||
OpusEncoder* Voice::createEncoder(int channels) {
|
||||
int error;
|
||||
OpusEncoder* encoder = opus_encoder_create(VOICE_SAMPLE_RATE, channels, OPUS_APPLICATION_AUDIO, &error);
|
||||
if (error != OPUS_OK)
|
||||
throw VoiceException::format("Could not create encoder: {}", opus_strerror(error));
|
||||
else
|
||||
return encoder;
|
||||
}
|
||||
|
||||
void Voice::resetEncoder() {
|
||||
int channels = encoderChannels();
|
||||
MutexLocker locker(m_threadMutex);
|
||||
m_encoder.reset(createEncoder(channels));
|
||||
opus_encoder_ctl(m_encoder.get(), OPUS_SET_BITRATE(channels == 2 ? 50000 : 24000));
|
||||
}
|
||||
|
||||
void Voice::resetDevice() {
|
||||
if (shouldEnableInput())
|
||||
openDevice();
|
||||
else
|
||||
closeDevice();
|
||||
}
|
||||
|
||||
void Voice::openDevice() {
|
||||
closeDevice();
|
||||
|
||||
m_applicationController->openAudioInputDevice(
|
||||
m_deviceName ? m_deviceName->utf8Ptr() : nullptr,
|
||||
VOICE_SAMPLE_RATE,
|
||||
m_deviceChannels = encoderChannels(),
|
||||
this,
|
||||
[](void* userdata, uint8_t* stream, int len) {
|
||||
((Voice*)(userdata))->readAudioData(stream, len);
|
||||
}
|
||||
);
|
||||
|
||||
m_deviceOpen = true;
|
||||
}
|
||||
|
||||
void Voice::closeDevice() {
|
||||
if (!m_deviceOpen)
|
||||
return;
|
||||
|
||||
m_applicationController->closeAudioInputDevice();
|
||||
m_clientSpeaker->playing = false;
|
||||
m_clientSpeaker->decibelLevel = -96.0f;
|
||||
m_deviceOpen = false;
|
||||
}
|
||||
|
||||
bool Voice::playSpeaker(SpeakerPtr const& speaker, int channels) {
|
||||
if (speaker->playing || speaker->audioStream->samples.size() < speaker->minimumPlaySamples)
|
||||
return false;
|
||||
|
||||
if (!speaker->playing) {
|
||||
speaker->lastPlayTime = Time::monotonicMilliseconds();
|
||||
speaker->playing = true;
|
||||
MutexLocker lock(m_activeSpeakersMutex);
|
||||
m_activeSpeakers.insert(speaker);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Voice::thread() {
|
||||
while (true) {
|
||||
MutexLocker locker(m_threadMutex);
|
||||
|
||||
m_threadCond.wait(m_threadMutex);
|
||||
if (m_stopThread)
|
||||
return;
|
||||
|
||||
{
|
||||
MutexLocker locker(m_captureMutex);
|
||||
ByteArray encoded(VOICE_MAX_PACKET_SIZE, 0);
|
||||
size_t frameSamples = VOICE_FRAME_SIZE * (size_t)m_deviceChannels;
|
||||
while (m_capturedChunksFrames >= VOICE_FRAME_SIZE) {
|
||||
std::vector<opus_int16> samples;
|
||||
samples.reserve(frameSamples);
|
||||
size_t samplesLeft = frameSamples;
|
||||
while (samplesLeft && !m_capturedChunks.empty()) {
|
||||
auto& front = m_capturedChunks.front();
|
||||
if (front.exhausted())
|
||||
m_capturedChunks.pop();
|
||||
else
|
||||
samplesLeft -= front.takeSamples(samples, samplesLeft);
|
||||
}
|
||||
m_capturedChunksFrames -= VOICE_FRAME_SIZE;
|
||||
|
||||
if (m_inputVolume != 1.0f) {
|
||||
for (size_t i = 0; i != samples.size(); ++i)
|
||||
samples[i] *= m_inputVolume;
|
||||
}
|
||||
|
||||
if (int encodedSize = opus_encode(m_encoder.get(), samples.data(), VOICE_FRAME_SIZE, (unsigned char*)encoded.ptr(), encoded.size())) {
|
||||
if (encodedSize == 1)
|
||||
continue;
|
||||
|
||||
encoded.resize(encodedSize);
|
||||
|
||||
{
|
||||
MutexLocker lock(m_encodeMutex);
|
||||
m_encodedChunks.emplace_back(move(encoded));
|
||||
m_encodedChunksLength += encodedSize;
|
||||
|
||||
encoded = ByteArray(VOICE_MAX_PACKET_SIZE, 0);
|
||||
}
|
||||
|
||||
//Logger::info("Voice: encoded Opus chunk {} samples -> {} bytes", frameSamples, encodedSize);
|
||||
}
|
||||
else if (encodedSize < 0)
|
||||
Logger::error("Voice: Opus encode error {}", opus_strerror(encodedSize));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
locker.unlock();
|
||||
Thread::yield();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
226
source/frontend/StarVoice.hpp
Normal file
@ -0,0 +1,226 @@
|
||||
#ifndef STAR_VOICE_HPP
|
||||
#define STAR_VOICE_HPP
|
||||
#include "StarJson.hpp"
|
||||
#include "StarBiMap.hpp"
|
||||
#include "StarException.hpp"
|
||||
#include "StarGameTypes.hpp"
|
||||
#include "StarMaybe.hpp"
|
||||
#include "StarThread.hpp"
|
||||
#include "StarDataStreamDevices.hpp"
|
||||
#include "StarApplicationController.hpp"
|
||||
|
||||
#include <queue>
|
||||
|
||||
struct OpusDecoder;
|
||||
typedef std::unique_ptr<OpusDecoder, void(*)(OpusDecoder*)> OpusDecoderPtr;
|
||||
struct OpusEncoder;
|
||||
typedef std::unique_ptr<OpusEncoder, void(*)(OpusEncoder*)> OpusEncoderPtr;
|
||||
|
||||
namespace Star {
|
||||
|
||||
String const VoiceBroadcastPrefix = "Voice\0"s;
|
||||
|
||||
STAR_EXCEPTION(VoiceException, StarException);
|
||||
|
||||
enum class VoiceInputMode : uint8_t { VoiceActivity, PushToTalk };
|
||||
extern EnumMap<VoiceInputMode> const VoiceInputModeNames;
|
||||
|
||||
enum class VoiceChannelMode: uint8_t { Mono = 1, Stereo = 2 };
|
||||
extern EnumMap<VoiceChannelMode> const VoiceChannelModeNames;
|
||||
|
||||
STAR_CLASS(Voice);
|
||||
STAR_CLASS(VoiceAudioStream);
|
||||
STAR_CLASS(ApplicationController);
|
||||
|
||||
struct VoiceAudioChunk {
|
||||
std::unique_ptr<int16_t[]> data;
|
||||
size_t remaining;
|
||||
size_t offset = 0;
|
||||
|
||||
VoiceAudioChunk(int16_t* ptr, size_t size) {
|
||||
data.reset(ptr);
|
||||
remaining = size;
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
inline size_t takeSamples(std::vector<int16_t>& out, size_t count) {
|
||||
size_t toRead = min<size_t>(count, remaining);
|
||||
int16_t* start = data.get() + offset;
|
||||
out.insert(out.end(), start, start + toRead);
|
||||
offset += toRead;
|
||||
remaining -= toRead;
|
||||
return toRead;
|
||||
}
|
||||
|
||||
//this one's unsafe
|
||||
inline int16_t takeSample() {
|
||||
--remaining;
|
||||
return *(data.get() + offset++);
|
||||
}
|
||||
|
||||
inline bool exhausted() { return remaining == 0; }
|
||||
};
|
||||
|
||||
|
||||
class Voice {
|
||||
public:
|
||||
// Individual speakers are represented by their connection ID.
|
||||
typedef ConnectionId SpeakerId;
|
||||
|
||||
class Speaker {
|
||||
public:
|
||||
SpeakerId speakerId = 0;
|
||||
EntityId entityId = 0;
|
||||
|
||||
Vec2F position = Vec2F();
|
||||
String name = "Unnamed";
|
||||
|
||||
OpusDecoderPtr decoderMono;
|
||||
OpusDecoderPtr decoderStereo;
|
||||
VoiceAudioStreamPtr audioStream;
|
||||
Mutex mutex;
|
||||
|
||||
int64_t lastReceiveTime = 0;
|
||||
int64_t lastPlayTime = 0;
|
||||
float smoothDb = -96.0f;
|
||||
Array<float, 10> dbHistory = Array<float, 10>::filled(0);
|
||||
|
||||
atomic<bool> muted = false;
|
||||
atomic<bool> playing = 0;
|
||||
atomic<float> decibelLevel = -96.0f;
|
||||
atomic<float> volume = 1.0f;
|
||||
atomic<Array<float, 2>> channelVolumes = Array<float, 2>::filled(1);
|
||||
|
||||
unsigned int minimumPlaySamples = 4096;
|
||||
|
||||
Speaker(SpeakerId speakerId);
|
||||
Json toJson() const;
|
||||
};
|
||||
|
||||
typedef std::shared_ptr<Speaker> SpeakerPtr;
|
||||
|
||||
// Get pointer to the singleton Voice instance, if it exists. Otherwise,
|
||||
// returns nullptr.
|
||||
static Voice* singletonPtr();
|
||||
|
||||
// Gets reference to Voice singleton, throws VoiceException if root
|
||||
// is not initialized.
|
||||
static Voice& singleton();
|
||||
|
||||
Voice(ApplicationControllerPtr appController);
|
||||
~Voice();
|
||||
|
||||
Voice(Voice const&) = delete;
|
||||
Voice& operator=(Voice const&) = delete;
|
||||
|
||||
void init();
|
||||
|
||||
void loadJson(Json const& config, bool skipSave = false);
|
||||
Json saveJson() const;
|
||||
|
||||
void save() const;
|
||||
void scheduleSave();
|
||||
|
||||
// Sets the local speaker ID and returns the local speaker. Must be called upon loading into a world.
|
||||
SpeakerPtr setLocalSpeaker(SpeakerId speakerId);
|
||||
SpeakerPtr localSpeaker();
|
||||
SpeakerPtr speaker(SpeakerId speakerId);
|
||||
HashMap<SpeakerId, SpeakerPtr>& speakers();
|
||||
List<Voice::SpeakerPtr> sortedSpeakers(bool onlyPlaying);
|
||||
void clearSpeakers();
|
||||
|
||||
// Called when receiving input audio data from SDL, on its own thread.
|
||||
void readAudioData(uint8_t* stream, int len);
|
||||
|
||||
// Called to mix voice audio with the game.
|
||||
void mix(int16_t* buffer, size_t frames, unsigned channels);
|
||||
|
||||
typedef function<float(unsigned, Vec2F, float)> PositionalAttenuationFunction;
|
||||
void update(PositionalAttenuationFunction positionalAttenuationFunction = {});
|
||||
|
||||
void setDeviceName(Maybe<String> device);
|
||||
StringList availableDevices();
|
||||
|
||||
int send(DataStreamBuffer& out, size_t budget = 0);
|
||||
bool receive(SpeakerPtr speaker, std::string_view view);
|
||||
|
||||
// Must be called every frame with input state, expires after 1s.
|
||||
void setInput(bool input = true);
|
||||
|
||||
inline int encoderChannels() const { return (int)m_channelMode; }
|
||||
|
||||
static OpusDecoder* createDecoder(int channels);
|
||||
static OpusEncoder* createEncoder(int channels);
|
||||
private:
|
||||
static Voice* s_singleton;
|
||||
void resetEncoder();
|
||||
void resetDevice();
|
||||
void openDevice();
|
||||
void closeDevice();
|
||||
inline bool shouldEnableInput() const { return m_enabled && m_inputEnabled; }
|
||||
|
||||
bool playSpeaker(SpeakerPtr const& speaker, int channels);
|
||||
|
||||
void thread();
|
||||
|
||||
SpeakerId m_speakerId = 0;
|
||||
SpeakerPtr m_clientSpeaker;
|
||||
HashMap<SpeakerId, SpeakerPtr> m_speakers;
|
||||
|
||||
Mutex m_activeSpeakersMutex;
|
||||
HashSet<SpeakerPtr> m_activeSpeakers;
|
||||
|
||||
|
||||
|
||||
OpusEncoderPtr m_encoder;
|
||||
|
||||
float m_outputVolume = 1.0f;
|
||||
float m_inputVolume = 1.0f;
|
||||
float m_threshold = -50.0f;
|
||||
|
||||
int64_t m_lastSentTime = 0;
|
||||
int64_t m_lastInputTime = 0;
|
||||
int64_t m_lastThresholdTime = 0;
|
||||
int64_t m_nextSaveTime = 0;
|
||||
bool m_enabled = true;
|
||||
bool m_inputEnabled = false;
|
||||
bool m_loopback = false;
|
||||
|
||||
int m_deviceChannels = 1;
|
||||
bool m_deviceOpen = false;
|
||||
Maybe<String> m_deviceName;
|
||||
VoiceInputMode m_inputMode;
|
||||
VoiceChannelMode m_channelMode;
|
||||
|
||||
ThreadFunction<void> m_thread;
|
||||
Mutex m_threadMutex;
|
||||
ConditionVariable m_threadCond;
|
||||
atomic<bool> m_stopThread;
|
||||
|
||||
std::vector<int16_t> m_decodeBuffer;
|
||||
std::vector<int16_t> m_resampleBuffer;
|
||||
|
||||
ApplicationControllerPtr m_applicationController;
|
||||
|
||||
struct EncodedChunk {
|
||||
std::unique_ptr<unsigned char[]> data;
|
||||
size_t size;
|
||||
|
||||
EncodedChunk(unsigned char* _data, size_t len) {
|
||||
data.reset(_data);
|
||||
size = len;
|
||||
}
|
||||
};
|
||||
|
||||
Mutex m_encodeMutex;
|
||||
std::vector<ByteArray> m_encodedChunks;
|
||||
size_t m_encodedChunksLength = 0;
|
||||
|
||||
Mutex m_captureMutex;
|
||||
std::queue<VoiceAudioChunk> m_capturedChunks;
|
||||
size_t m_capturedChunksFrames = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
45
source/frontend/StarVoiceLuaBindings.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include "StarLuaConverters.hpp"
|
||||
#include "StarVoiceLuaBindings.hpp"
|
||||
#include "StarVoice.hpp"
|
||||
|
||||
|
||||
namespace Star {
|
||||
|
||||
typedef Voice::SpeakerId SpeakerId;
|
||||
LuaCallbacks LuaBindings::makeVoiceCallbacks() {
|
||||
LuaCallbacks callbacks;
|
||||
|
||||
auto voice = Voice::singletonPtr();
|
||||
|
||||
callbacks.registerCallbackWithSignature<StringList>("devices", bind(&Voice::availableDevices, voice));
|
||||
callbacks.registerCallback( "getSettings", [voice]() -> Json { return voice->saveJson(); });
|
||||
callbacks.registerCallback("mergeSettings", [voice](Json const& settings) { voice->loadJson(settings); });
|
||||
// i have an alignment addiction i'm so sorry
|
||||
callbacks.registerCallback("setSpeakerMuted", [voice](SpeakerId speakerId, bool muted) { voice->speaker(speakerId)->muted = muted; });
|
||||
callbacks.registerCallback( "speakerMuted", [voice](SpeakerId speakerId) { return (bool)voice->speaker(speakerId)->muted; });
|
||||
// it just looks so neat to me!!
|
||||
callbacks.registerCallback("setSpeakerVolume", [voice](SpeakerId speakerId, float volume) { voice->speaker(speakerId)->volume = volume; });
|
||||
callbacks.registerCallback( "speakerVolume", [voice](SpeakerId speakerId) { return (float)voice->speaker(speakerId)->volume; });
|
||||
|
||||
callbacks.registerCallback("speakerPosition", [voice](SpeakerId speakerId) { return voice->speaker(speakerId)->position; });
|
||||
|
||||
callbacks.registerCallback("speaker", [voice](Maybe<SpeakerId> speakerId) {
|
||||
if (speakerId)
|
||||
return voice->speaker(*speakerId)->toJson();
|
||||
else
|
||||
return voice->localSpeaker()->toJson();
|
||||
});
|
||||
|
||||
callbacks.registerCallback("speakers", [voice](Maybe<bool> onlyPlaying) -> List<Json> {
|
||||
List<Json> list;
|
||||
|
||||
for (auto& speaker : voice->sortedSpeakers(onlyPlaying.value(true)))
|
||||
list.append(speaker->toJson());
|
||||
|
||||
return list;
|
||||
});
|
||||
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
}
|
16
source/frontend/StarVoiceLuaBindings.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef STAR_VOICE_LUA_BINDINGS_HPP
|
||||
#define STAR_VOICE_LUA_BINDINGS_HPP
|
||||
|
||||
#include "StarLua.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(Voice);
|
||||
|
||||
namespace LuaBindings {
|
||||
LuaCallbacks makeVoiceCallbacks();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
23
source/frontend/StarVoiceSettingsMenu.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include "StarVoiceSettingsMenu.hpp"
|
||||
#include "StarVoiceLuaBindings.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
VoiceSettingsMenu::VoiceSettingsMenu(Json const& config) : BaseScriptPane(config) {
|
||||
m_script.setLuaRoot(make_shared<LuaRoot>());
|
||||
m_script.addCallbacks("voice", LuaBindings::makeVoiceCallbacks());
|
||||
}
|
||||
|
||||
void VoiceSettingsMenu::show() {
|
||||
BaseScriptPane::show();
|
||||
}
|
||||
|
||||
void VoiceSettingsMenu::displayed() {
|
||||
BaseScriptPane::displayed();
|
||||
}
|
||||
|
||||
void VoiceSettingsMenu::dismissed() {
|
||||
BaseScriptPane::dismissed();
|
||||
}
|
||||
|
||||
}
|
24
source/frontend/StarVoiceSettingsMenu.hpp
Normal file
@ -0,0 +1,24 @@
|
||||
#ifndef STAR_VOICE_SETTINGS_MENU_HPP
|
||||
#define STAR_VOICE_SETTINGS_MENU_HPP
|
||||
|
||||
#include "StarBaseScriptPane.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
STAR_CLASS(VoiceSettingsMenu);
|
||||
|
||||
class VoiceSettingsMenu : public BaseScriptPane {
|
||||
public:
|
||||
VoiceSettingsMenu(Json const& config);
|
||||
|
||||
virtual void show() override;
|
||||
void displayed() override;
|
||||
void dismissed() override;
|
||||
|
||||
private:
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
@ -316,10 +316,6 @@ Input::InputState* Input::bindStatePtr(String const& categoryId, String const& b
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Input::InputState* Input::inputStatePtr(InputVariant key) {
|
||||
return m_inputStates.ptr(key);
|
||||
}
|
||||
|
||||
Input* Input::s_singleton;
|
||||
|
||||
Input* Input::singletonPtr() {
|
||||
@ -361,28 +357,17 @@ List<std::pair<InputEvent, bool>> const& Input::inputEventsThisFrame() const {
|
||||
|
||||
|
||||
void Input::reset() {
|
||||
m_inputEvents.resize(0); // keeps reserved memory
|
||||
{
|
||||
auto it = m_inputStates.begin();
|
||||
while (it != m_inputStates.end()) {
|
||||
if (it->second.held) {
|
||||
it->second.reset();
|
||||
++it;
|
||||
}
|
||||
else it = m_inputStates.erase(it);
|
||||
}
|
||||
}
|
||||
m_inputEvents.clear();
|
||||
auto eraseCond = [](auto& p) {
|
||||
if (p.second.held)
|
||||
p.second.reset();
|
||||
return !p.second.held;
|
||||
};
|
||||
|
||||
{
|
||||
auto it = m_bindStates.begin();
|
||||
while (it != m_bindStates.end()) {
|
||||
if (it->second.held) {
|
||||
it->second.reset();
|
||||
++it;
|
||||
}
|
||||
else it = m_bindStates.erase(it);
|
||||
}
|
||||
}
|
||||
eraseWhere(m_keyStates, eraseCond);
|
||||
eraseWhere(m_mouseStates, eraseCond);
|
||||
eraseWhere(m_controllerStates, eraseCond);
|
||||
eraseWhere(m_bindStates, eraseCond);
|
||||
}
|
||||
|
||||
void Input::update() {
|
||||
@ -397,7 +382,10 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
|
||||
m_pressedMods |= *keyToMod;
|
||||
|
||||
if (!gameProcessed && !m_textInputActive) {
|
||||
m_inputStates[keyDown->key].press();
|
||||
auto& state = m_keyStates[keyDown->key];
|
||||
if (keyToMod)
|
||||
state.mods |= *keyToMod;
|
||||
state.press();
|
||||
|
||||
if (auto binds = m_bindMappings.ptr(keyDown->key)) {
|
||||
for (auto bind : filterBindEntries(*binds, keyDown->mods))
|
||||
@ -410,8 +398,11 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
|
||||
m_pressedMods &= ~*keyToMod;
|
||||
|
||||
// We need to be able to release input even when gameProcessed is true, but only if it's already down.
|
||||
if (auto state = m_inputStates.ptr(keyUp->key))
|
||||
if (auto state = m_keyStates.ptr(keyUp->key)) {
|
||||
if (keyToMod)
|
||||
state->mods &= ~*keyToMod;
|
||||
state->release();
|
||||
}
|
||||
|
||||
if (auto binds = m_bindMappings.ptr(keyUp->key)) {
|
||||
for (auto& bind : *binds) {
|
||||
@ -421,7 +412,9 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
|
||||
}
|
||||
} else if (auto mouseDown = input.ptr<MouseButtonDownEvent>()) {
|
||||
if (!gameProcessed) {
|
||||
m_inputStates[mouseDown->mouseButton].press();
|
||||
auto& state = m_mouseStates[mouseDown->mouseButton];
|
||||
state.pressPositions.append(mouseDown->mousePosition);
|
||||
state.press();
|
||||
|
||||
if (auto binds = m_bindMappings.ptr(mouseDown->mouseButton)) {
|
||||
for (auto bind : filterBindEntries(*binds, m_pressedMods))
|
||||
@ -429,8 +422,10 @@ bool Input::handleInput(InputEvent const& input, bool gameProcessed) {
|
||||
}
|
||||
}
|
||||
} else if (auto mouseUp = input.ptr<MouseButtonUpEvent>()) {
|
||||
if (auto state = m_inputStates.ptr(mouseUp->mouseButton))
|
||||
if (auto state = m_mouseStates.ptr(mouseUp->mouseButton)) {
|
||||
state->releasePositions.append(mouseUp->mousePosition);
|
||||
state->release();
|
||||
}
|
||||
|
||||
if (auto binds = m_bindMappings.ptr(mouseUp->mouseButton)) {
|
||||
for (auto& bind : *binds) {
|
||||
@ -497,10 +492,10 @@ void Input::setTextInputActive(bool active) {
|
||||
}
|
||||
|
||||
Maybe<unsigned> Input::bindDown(String const& categoryId, String const& bindId) {
|
||||
if (auto state = bindStatePtr(categoryId, bindId))
|
||||
if (auto state = bindStatePtr(categoryId, bindId)) {
|
||||
if (state->presses)
|
||||
return state->presses;
|
||||
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -512,10 +507,52 @@ bool Input::bindHeld(String const& categoryId, String const& bindId) {
|
||||
}
|
||||
|
||||
Maybe<unsigned> Input::bindUp(String const& categoryId, String const& bindId) {
|
||||
if (auto state = bindStatePtr(categoryId, bindId))
|
||||
if (auto state = bindStatePtr(categoryId, bindId)) {
|
||||
if (state->releases)
|
||||
return state->releases;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<unsigned> Input::keyDown(Key key, Maybe<KeyMod> keyMod) {
|
||||
if (auto state = m_keyStates.ptr(key)) {
|
||||
if (state->presses && (!keyMod || compareKeyMod(*keyMod, state->mods)))
|
||||
return state->presses;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Input::keyHeld(Key key) {
|
||||
auto state = m_keyStates.ptr(key);
|
||||
return state && state->held;
|
||||
}
|
||||
|
||||
Maybe<unsigned> Input::keyUp(Key key) {
|
||||
if (auto state = m_keyStates.ptr(key)) {
|
||||
if (state->releases)
|
||||
return state->releases;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<List<Vec2I>> Input::mouseDown(MouseButton button) {
|
||||
if (auto state = m_mouseStates.ptr(button)) {
|
||||
if (state->presses)
|
||||
return state->pressPositions;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool Input::mouseHeld(MouseButton button) {
|
||||
auto state = m_mouseStates.ptr(button);
|
||||
return state && state->held;
|
||||
}
|
||||
|
||||
Maybe<List<Vec2I>> Input::mouseUp(MouseButton button) {
|
||||
if (auto state = m_mouseStates.ptr(button)) {
|
||||
if (state->releases)
|
||||
return state->releasePositions;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,17 @@ public:
|
||||
inline void release() { released = ++releases; held = false; }
|
||||
};
|
||||
|
||||
struct KeyInputState : InputState {
|
||||
KeyMod mods = KeyMod::NoMod;
|
||||
};
|
||||
|
||||
struct MouseInputState : InputState {
|
||||
List<Vec2I> pressPositions;
|
||||
List<Vec2I> releasePositions;
|
||||
};
|
||||
|
||||
typedef InputState ControllerInputState;
|
||||
|
||||
// Get pointer to the singleton Input instance, if it exists. Otherwise,
|
||||
// returns nullptr.
|
||||
static Input* singletonPtr();
|
||||
@ -152,6 +163,14 @@ public:
|
||||
bool bindHeld(String const& categoryId, String const& bindId);
|
||||
Maybe<unsigned> bindUp (String const& categoryId, String const& bindId);
|
||||
|
||||
Maybe<unsigned> keyDown(Key key, Maybe<KeyMod> keyMod);
|
||||
bool keyHeld(Key key);
|
||||
Maybe<unsigned> keyUp (Key key);
|
||||
|
||||
Maybe<List<Vec2I>> mouseDown(MouseButton button);
|
||||
bool mouseHeld(MouseButton button);
|
||||
Maybe<List<Vec2I>> mouseUp (MouseButton button);
|
||||
|
||||
void resetBinds(String const& categoryId, String const& bindId);
|
||||
void setBinds(String const& categoryId, String const& bindId, Json const& binds);
|
||||
Json getDefaultBinds(String const& categoryId, String const& bindId);
|
||||
@ -163,7 +182,6 @@ private:
|
||||
BindEntry& bindEntry(String const& categoryId, String const& bindId);
|
||||
|
||||
InputState* bindStatePtr(String const& categoryId, String const& bindId);
|
||||
InputState* inputStatePtr(InputVariant key);
|
||||
|
||||
static Input* s_singleton;
|
||||
|
||||
@ -179,7 +197,9 @@ private:
|
||||
|
||||
// Per-frame input state maps.
|
||||
//Input states
|
||||
HashMap<InputVariant, InputState> m_inputStates;
|
||||
HashMap<Key, KeyInputState> m_keyStates;
|
||||
HashMap<MouseButton, MouseInputState> m_mouseStates;
|
||||
HashMap<ControllerButton, ControllerInputState> m_controllerStates;
|
||||
//Bind states
|
||||
HashMap<BindEntry const*, InputState> m_bindStates;
|
||||
|
||||
|
@ -383,6 +383,15 @@ void NetworkedAnimator::setGlobalTag(String tagName, String tagValue) {
|
||||
m_globalTags.set(move(tagName), move(tagValue));
|
||||
}
|
||||
|
||||
void NetworkedAnimator::removeGlobalTag(String const& tagName) {
|
||||
m_globalTags.remove(tagName);
|
||||
}
|
||||
|
||||
String const* NetworkedAnimator::globalTagPtr(String const& tagName) const {
|
||||
return m_globalTags.ptr(tagName);
|
||||
}
|
||||
|
||||
|
||||
void NetworkedAnimator::setPartTag(String const& partType, String tagName, String tagValue) {
|
||||
m_partTags[partType].set(move(tagName), move(tagValue));
|
||||
}
|
||||
|
@ -115,6 +115,8 @@ public:
|
||||
// Drawables can also have a <frame> tag which will be set to whatever the
|
||||
// current state frame is (1 indexed, so the first frame is 1).
|
||||
void setGlobalTag(String tagName, String tagValue);
|
||||
void removeGlobalTag(String const& tagName);
|
||||
String const* globalTagPtr(String const& tagName) const;
|
||||
void setPartTag(String const& partType, String tagName, String tagValue);
|
||||
|
||||
void setProcessingDirectives(Directives const& directives);
|
||||
|
@ -2464,4 +2464,53 @@ Vec2F Player::cameraPosition() {
|
||||
return position();
|
||||
}
|
||||
|
||||
NetworkedAnimatorPtr Player::effectsAnimator() {
|
||||
return m_effectsAnimator;
|
||||
}
|
||||
|
||||
const String secretProprefix = "\0JsonProperty\0"s;
|
||||
|
||||
Maybe<StringView> Player::getSecretPropertyView(String const& name) const {
|
||||
if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) {
|
||||
auto& view = tag->utf8();
|
||||
DataStreamExternalBuffer buffer(view.data(), view.size());
|
||||
try {
|
||||
uint8_t typeIndex = buffer.read<uint8_t>() - 1;
|
||||
if ((Json::Type)typeIndex == Json::Type::String) {
|
||||
size_t len = buffer.readVlqU();
|
||||
size_t pos = buffer.pos();
|
||||
if (pos + len == buffer.size())
|
||||
return StringView(buffer.ptr() + pos, len);
|
||||
}
|
||||
}
|
||||
catch (StarException const& e) {}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Json Player::getSecretProperty(String const& name, Json defaultValue) const {
|
||||
if (auto tag = m_effectsAnimator->globalTagPtr(secretProprefix + name)) {
|
||||
DataStreamExternalBuffer buffer(tag->utf8Ptr(), tag->utf8Size());
|
||||
try
|
||||
{ return buffer.read<Json>(); }
|
||||
catch (StarException const& e)
|
||||
{ Logger::error("Exception reading secret player property '{}': {}", name, e.what()); }
|
||||
}
|
||||
|
||||
return move(defaultValue);
|
||||
}
|
||||
|
||||
void Player::setSecretProperty(String const& name, Json const& value) {
|
||||
if (value) {
|
||||
DataStreamBuffer ds;
|
||||
ds.write(value);
|
||||
auto& data = ds.data();
|
||||
m_effectsAnimator->setGlobalTag(secretProprefix + name, String(data.ptr(), data.size()));
|
||||
}
|
||||
else
|
||||
m_effectsAnimator->removeGlobalTag(secretProprefix + name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -447,6 +447,28 @@ public:
|
||||
|
||||
using Entity::setTeam;
|
||||
|
||||
NetworkedAnimatorPtr effectsAnimator();
|
||||
|
||||
// We need to store ephemeral/large/always-changing networked properties that other clients can read. Candidates:
|
||||
// genericProperties:
|
||||
// Non-starter, is not networked.
|
||||
// statusProperties:
|
||||
// Nope! Changes to the status properties aren't networked efficiently - one change resends the whole map.
|
||||
// We can't fix that because it would break compatibility with vanilla servers.
|
||||
// effectsAnimator's globalTags:
|
||||
// Cursed, but viable.
|
||||
// Efficient networking due to using a NetElementMapWrapper.
|
||||
// Unfortunately values are Strings, so to work with Json we need to serialize/deserialize. Whatever.
|
||||
// Additionally, this is compatible with vanilla networking.
|
||||
// I call this a 'secret property'.
|
||||
|
||||
// If the secret property exists as a serialized Json string, returns a view to it without deserializing.
|
||||
Maybe<StringView> getSecretPropertyView(String const& name) const;
|
||||
// Gets a secret Json property. It will be de-serialized.
|
||||
Json getSecretProperty(String const& name, Json defaultValue = Json()) const;
|
||||
// Sets a secret Json property. It will be serialized.
|
||||
void setSecretProperty(String const& name, Json const& value);
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
Idle,
|
||||
|
@ -30,6 +30,7 @@ UniverseClient::UniverseClient(PlayerStoragePtr playerStorage, StatisticsPtr sta
|
||||
m_playerStorage = move(playerStorage);
|
||||
m_statistics = move(statistics);
|
||||
m_pause = false;
|
||||
m_luaRoot = make_shared<LuaRoot>();
|
||||
reset();
|
||||
}
|
||||
|
||||
@ -86,7 +87,7 @@ Maybe<String> UniverseClient::connect(UniverseConnection connection, bool allowA
|
||||
return String(strf("Join failed! Server does not support connections with protocol version {}", StarProtocolVersion));
|
||||
|
||||
connection.pushSingle(make_shared<ClientConnectPacket>(Root::singleton().assets()->digest(), allowAssetsMismatch, m_mainPlayer->uuid(), m_mainPlayer->name(),
|
||||
m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), ShipUpgrades(m_mainPlayer->shipUpgrades()),
|
||||
m_mainPlayer->species(), m_playerStorage->loadShipData(m_mainPlayer->uuid()), m_mainPlayer->shipUpgrades(),
|
||||
m_mainPlayer->log()->introComplete(), account));
|
||||
connection.sendAll(timeout);
|
||||
|
||||
@ -219,8 +220,11 @@ void UniverseClient::update() {
|
||||
|
||||
m_statistics->update();
|
||||
|
||||
if (!m_pause)
|
||||
if (!m_pause) {
|
||||
m_worldClient->update();
|
||||
for (auto& p : m_scriptContexts)
|
||||
p.second->update();
|
||||
}
|
||||
m_connection->push(m_worldClient->getOutgoingPackets());
|
||||
|
||||
if (!m_pause)
|
||||
@ -444,6 +448,28 @@ void UniverseClient::setLuaCallbacks(String const& groupName, LuaCallbacks const
|
||||
m_worldClient->setLuaCallbacks(groupName, callbacks);
|
||||
}
|
||||
|
||||
void UniverseClient::startLua() {
|
||||
auto assets = Root::singleton().assets();
|
||||
for (auto& p : assets->json("/client.config:universeScriptContexts").toObject()) {
|
||||
auto scriptComponent = make_shared<ScriptComponent>();
|
||||
scriptComponent->setLuaRoot(m_luaRoot);
|
||||
scriptComponent->setScripts(jsonToStringList(p.second.toArray()));
|
||||
|
||||
for (auto& pair : m_luaCallbacks)
|
||||
scriptComponent->addCallbacks(pair.first, pair.second);
|
||||
|
||||
m_scriptContexts.set(p.first, scriptComponent);
|
||||
scriptComponent->init();
|
||||
}
|
||||
}
|
||||
|
||||
void UniverseClient::stopLua() {
|
||||
for (auto& p : m_scriptContexts)
|
||||
p.second->uninit();
|
||||
|
||||
m_scriptContexts.clear();
|
||||
}
|
||||
|
||||
ClockConstPtr UniverseClient::universeClock() const {
|
||||
return m_universeClock;
|
||||
}
|
||||
@ -539,6 +565,8 @@ void UniverseClient::handlePackets(List<PacketPtr> const& packets) {
|
||||
}
|
||||
|
||||
void UniverseClient::reset() {
|
||||
stopLua();
|
||||
|
||||
m_universeClock.reset();
|
||||
m_worldClient.reset();
|
||||
m_celestialDatabase.reset();
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "StarAiTypes.hpp"
|
||||
#include "StarSky.hpp"
|
||||
#include "StarUniverseConnection.hpp"
|
||||
#include "StarLuaComponents.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
@ -29,8 +30,8 @@ STAR_CLASS(CelestialDatabase);
|
||||
STAR_CLASS(JsonRpcInterface);
|
||||
STAR_CLASS(TeamClient);
|
||||
STAR_CLASS(QuestManager);
|
||||
|
||||
STAR_CLASS(UniverseClient);
|
||||
STAR_CLASS(LuaRoot);
|
||||
|
||||
class UniverseClient {
|
||||
public:
|
||||
@ -86,6 +87,8 @@ public:
|
||||
uint16_t maxPlayers();
|
||||
|
||||
void setLuaCallbacks(String const& groupName, LuaCallbacks const& callbacks);
|
||||
void startLua();
|
||||
void stopLua();
|
||||
|
||||
ClockConstPtr universeClock() const;
|
||||
CelestialLogConstPtr celestialLog() const;
|
||||
@ -141,6 +144,12 @@ private:
|
||||
List<ChatReceivedMessage> m_pendingMessages;
|
||||
|
||||
Maybe<String> m_disconnectReason;
|
||||
|
||||
LuaRootPtr m_luaRoot;
|
||||
|
||||
typedef LuaUpdatableComponent<LuaBaseComponent> ScriptComponent;
|
||||
typedef shared_ptr<ScriptComponent> ScriptComponentPtr;
|
||||
StringMap<ScriptComponentPtr> m_scriptContexts;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -20,11 +20,14 @@
|
||||
#include "StarWorldTemplate.hpp"
|
||||
#include "StarStoredFunctions.hpp"
|
||||
#include "StarInspectableEntity.hpp"
|
||||
#include "StarCurve25519.hpp"
|
||||
|
||||
namespace Star {
|
||||
|
||||
const float WorldClient::DropDist = 6.0f;
|
||||
const std::string SECRET_BROADCAST_PUBLIC_KEY = "SecretBroadcastPublicKey";
|
||||
const std::string SECRET_BROADCAST_PREFIX = "\0Broadcast\0"s;
|
||||
|
||||
const float WorldClient::DropDist = 6.0f;
|
||||
WorldClient::WorldClient(PlayerPtr mainPlayer) {
|
||||
auto& root = Root::singleton();
|
||||
auto assets = root.assets();
|
||||
@ -792,7 +795,50 @@ void WorldClient::handleIncomingPackets(List<PacketPtr> const& packets) {
|
||||
m_damageManager->pushRemoteDamageRequest(damage->remoteDamageRequest);
|
||||
|
||||
} else if (auto damage = as<DamageNotificationPacket>(packet)) {
|
||||
std::string_view view(damage->remoteDamageNotification.damageNotification.targetMaterialKind.utf8());
|
||||
static const size_t FULL_SIZE = SECRET_BROADCAST_PREFIX.size() + Curve25519::SignatureSize;
|
||||
static const std::string LEGACY_VOICE_PREFIX = "data\0voice\0"s;
|
||||
|
||||
if (view.size() >= FULL_SIZE && view.rfind(SECRET_BROADCAST_PREFIX, 0) != NPos) {
|
||||
// this is actually a secret broadcast!!
|
||||
if (auto player = m_entityMap->get<Player>(damage->remoteDamageNotification.sourceEntityId)) {
|
||||
if (auto publicKey = player->getSecretPropertyView(SECRET_BROADCAST_PUBLIC_KEY)) {
|
||||
if (publicKey->utf8Size() == Curve25519::PublicKeySize) {
|
||||
auto signature = view.substr(SECRET_BROADCAST_PREFIX.size(), Curve25519::SignatureSize);
|
||||
|
||||
auto rawBroadcast = view.substr(FULL_SIZE);
|
||||
if (Curve25519::verify(
|
||||
(uint8_t const*)signature.data(),
|
||||
(uint8_t const*)publicKey->utf8Ptr(),
|
||||
(void*)rawBroadcast.data(),
|
||||
rawBroadcast.size()
|
||||
)) {
|
||||
handleSecretBroadcast(player, rawBroadcast);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (view.size() > 75 && view.rfind(LEGACY_VOICE_PREFIX, 0) != NPos) {
|
||||
// this is a StarExtensions voice packet
|
||||
// (remove this and stop transmitting like this once most SE features are ported over)
|
||||
if (auto player = m_entityMap->get<Player>(damage->remoteDamageNotification.sourceEntityId)) {
|
||||
if (auto publicKey = player->effectsAnimator()->globalTagPtr("\0SE_VOICE_SIGNING_KEY"s)) {
|
||||
auto rawData = view.substr(75);
|
||||
if (m_broadcastCallback && Curve25519::verify(
|
||||
(uint8_t const*)view.data() + LEGACY_VOICE_PREFIX.size(),
|
||||
(uint8_t const*)publicKey->utf8Ptr(),
|
||||
(void*)rawData.data(),
|
||||
rawData.size()
|
||||
)) {
|
||||
m_broadcastCallback(player, "Voice\0"s + rawData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_damageManager->pushRemoteDamageNotification(damage->remoteDamageNotification);
|
||||
}
|
||||
|
||||
} else if (auto entityMessagePacket = as<EntityMessagePacket>(packet)) {
|
||||
EntityPtr entity;
|
||||
@ -917,6 +963,14 @@ void WorldClient::update() {
|
||||
}
|
||||
}
|
||||
|
||||
// Secret broadcasts are transmitted through DamageNotifications for vanilla server compatibility.
|
||||
// Because DamageNotification packets are spoofable, we have to sign the data so other clients can validate that it is legitimate.
|
||||
auto& publicKey = Curve25519::publicKey();
|
||||
String publicKeyString((const char*)publicKey.data(), publicKey.size());
|
||||
m_mainPlayer->setSecretProperty(SECRET_BROADCAST_PUBLIC_KEY, publicKeyString);
|
||||
// Temporary: Backwards compatibility with StarExtensions
|
||||
m_mainPlayer->effectsAnimator()->setGlobalTag("\0SE_VOICE_SIGNING_KEY"s, publicKeyString);
|
||||
|
||||
++m_currentStep;
|
||||
//m_interpolationTracker.update(m_currentStep);
|
||||
m_interpolationTracker.update(Time::monotonicTime());
|
||||
@ -1176,6 +1230,10 @@ void WorldClient::waitForLighting() {
|
||||
MutexLocker lock(m_lightingMutex);
|
||||
}
|
||||
|
||||
WorldClient::BroadcastCallback& WorldClient::broadcastCallback() {
|
||||
return m_broadcastCallback;
|
||||
}
|
||||
|
||||
bool WorldClient::isTileProtected(Vec2I const& pos) const {
|
||||
if (!inWorld())
|
||||
return true;
|
||||
@ -1847,6 +1905,35 @@ void WorldClient::connectWire(WireConnection const& output, WireConnection const
|
||||
m_outgoingPackets.append(make_shared<ConnectWirePacket>(output, input));
|
||||
}
|
||||
|
||||
bool WorldClient::sendSecretBroadcast(StringView broadcast, bool raw) {
|
||||
if (!inWorld() || !m_mainPlayer || !m_mainPlayer->getSecretPropertyView(SECRET_BROADCAST_PUBLIC_KEY))
|
||||
return false;
|
||||
|
||||
auto signature = Curve25519::sign((void*)broadcast.utf8Ptr(), broadcast.utf8Size());
|
||||
|
||||
auto damageNotification = make_shared<DamageNotificationPacket>();
|
||||
auto& remDmg = damageNotification->remoteDamageNotification;
|
||||
auto& dmg = remDmg.damageNotification;
|
||||
|
||||
dmg.targetEntityId = dmg.sourceEntityId = remDmg.sourceEntityId = m_mainPlayer->entityId();
|
||||
dmg.damageDealt = dmg.healthLost = 0.0f;
|
||||
dmg.hitType = HitType::Hit;
|
||||
dmg.damageSourceKind = "nodamage";
|
||||
dmg.targetMaterialKind = raw ? broadcast : strf("{}{}{}", SECRET_BROADCAST_PREFIX, StringView((char*)&signature, sizeof(signature)), broadcast);
|
||||
dmg.position = m_mainPlayer->position();
|
||||
|
||||
m_outgoingPackets.emplace_back(move(damageNotification));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WorldClient::handleSecretBroadcast(PlayerPtr player, StringView broadcast) {
|
||||
if (m_broadcastCallback)
|
||||
return m_broadcastCallback(player, broadcast);
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void WorldClient::ClientRenderCallback::addDrawable(Drawable drawable, EntityRenderLayer renderLayer) {
|
||||
drawables[renderLayer].append(move(drawable));
|
||||
}
|
||||
|
@ -150,6 +150,12 @@ public:
|
||||
void disconnectAllWires(Vec2I wireEntityPosition, WireNode const& node);
|
||||
void connectWire(WireConnection const& output, WireConnection const& input);
|
||||
|
||||
// Functions for sending broadcast messages to other players that can receive them,
|
||||
// on completely vanilla servers by smuggling it through a DamageNotification.
|
||||
// It's cursed as fuck, but it works.
|
||||
bool sendSecretBroadcast(StringView broadcast, bool raw = false);
|
||||
bool handleSecretBroadcast(PlayerPtr player, StringView broadcast);
|
||||
|
||||
List<ChatAction> pullPendingChatActions();
|
||||
|
||||
WorldStructure const& centralStructure() const;
|
||||
@ -160,6 +166,8 @@ public:
|
||||
|
||||
void waitForLighting();
|
||||
|
||||
typedef std::function<bool(PlayerPtr, StringView)> BroadcastCallback;
|
||||
BroadcastCallback& broadcastCallback();
|
||||
private:
|
||||
static const float DropDist;
|
||||
|
||||
@ -339,6 +347,8 @@ private:
|
||||
HashMap<Uuid, RpcPromiseKeeper<InteractAction>> m_entityInteractionResponses;
|
||||
|
||||
List<PhysicsForceRegion> m_forceRegions;
|
||||
|
||||
BroadcastCallback m_broadcastCallback;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,23 @@ LuaCallbacks LuaBindings::makeInputCallbacks() {
|
||||
callbacks.registerCallbackWithSignature<bool, String, String>("bindHeld", bind(mem_fn(&Input::bindHeld), input, _1, _2));
|
||||
callbacks.registerCallbackWithSignature<Maybe<unsigned>, String, String>("bindUp", bind(mem_fn(&Input::bindUp), input, _1, _2));
|
||||
|
||||
callbacks.registerCallback("keyDown", [input](String const& keyName, Maybe<StringList>& const modNames) -> Maybe<unsigned> {
|
||||
Key key = KeyNames.getLeft(keyName);
|
||||
Maybe<KeyMod> mod;
|
||||
if (modNames) {
|
||||
mod = KeyMod::NoMod;
|
||||
for (auto& modName : *modNames)
|
||||
*mod |= KeyModNames.getLeft(modName);
|
||||
}
|
||||
return input->keyDown(key, mod);
|
||||
});
|
||||
callbacks.registerCallback("keyHeld", [input](String const& keyName) -> bool { return input->keyHeld(KeyNames.getLeft(keyName)); });
|
||||
callbacks.registerCallback("keyUp", [input](String const& keyName) -> Maybe<unsigned> { return input->keyUp( KeyNames.getLeft(keyName)); });
|
||||
|
||||
callbacks.registerCallback("mouseDown", [input](String const& buttonName) -> Maybe<List<Vec2I>> { return input->mouseDown(MouseButtonNames.getLeft(buttonName)); });
|
||||
callbacks.registerCallback("mouseHeld", [input](String const& buttonName) -> bool { return input->mouseHeld(MouseButtonNames.getLeft(buttonName)); });
|
||||
callbacks.registerCallback("mouseUp", [input](String const& buttonName) -> Maybe<List<Vec2I>> { return input->mouseUp( MouseButtonNames.getLeft(buttonName)); });
|
||||
|
||||
callbacks.registerCallbackWithSignature<void, String, String>("resetBinds", bind(mem_fn(&Input::resetBinds), input, _1, _2));
|
||||
callbacks.registerCallbackWithSignature<void, String, String, Json>("setBinds", bind(mem_fn(&Input::setBinds), input, _1, _2, _3));
|
||||
callbacks.registerCallbackWithSignature<Json, String, String>("getDefaultBinds", bind(mem_fn(&Input::getDefaultBinds), input, _1, _2));
|
||||
|
@ -106,6 +106,11 @@ LuaContext LuaRoot::createContext(StringList const& scriptPaths) {
|
||||
}
|
||||
});
|
||||
|
||||
newContext.set("loadstring", m_luaEngine->createFunction([newContext](String const& source, Maybe<String> const& name, Maybe<LuaValue> const& env) -> LuaFunction {
|
||||
String functionName = name ? strf("loadstring: {}", *name) : "loadstring";
|
||||
return newContext.engine().createFunctionFromSource(newContext.handleIndex(), source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr());
|
||||
}));
|
||||
|
||||
for (auto const& scriptPath : scriptPaths)
|
||||
cache->loadContextScript(newContext, scriptPath);
|
||||
|
||||
|
@ -88,20 +88,21 @@ bool CanvasWidget::sendEvent(InputEvent const& event) {
|
||||
return false;
|
||||
|
||||
auto& context = GuiContext::singleton();
|
||||
int interfaceScale = m_ignoreInterfaceScale ? 1 : context.interfaceScale();
|
||||
if (auto mouseButtonDown = event.ptr<MouseButtonDownEvent>()) {
|
||||
if (inMember(*context.mousePosition(event)) && m_captureMouse) {
|
||||
m_clickEvents.append({*context.mousePosition(event) - screenPosition(), mouseButtonDown->mouseButton, true});
|
||||
if (inMember(*context.mousePosition(event, interfaceScale)) && m_captureMouse) {
|
||||
m_clickEvents.append({*context.mousePosition(event, interfaceScale) - screenPosition(), mouseButtonDown->mouseButton, true});
|
||||
m_clickEvents.limitSizeBack(MaximumEventBuffer);
|
||||
return true;
|
||||
}
|
||||
} else if (auto mouseButtonUp = event.ptr<MouseButtonUpEvent>()) {
|
||||
if (m_captureMouse) {
|
||||
m_clickEvents.append({*context.mousePosition(event) - screenPosition(), mouseButtonUp->mouseButton, false});
|
||||
m_clickEvents.append({*context.mousePosition(event, interfaceScale) - screenPosition(), mouseButtonUp->mouseButton, false});
|
||||
m_clickEvents.limitSizeBack(MaximumEventBuffer);
|
||||
return true;
|
||||
}
|
||||
} else if (event.is<MouseMoveEvent>()) {
|
||||
m_mousePosition = *context.mousePosition(event) - screenPosition();
|
||||
m_mousePosition = *context.mousePosition(event, interfaceScale) - screenPosition();
|
||||
return false;
|
||||
} else if (auto keyDown = event.ptr<KeyDownEvent>()) {
|
||||
if (m_captureKeyboard) {
|
||||
@ -258,7 +259,7 @@ void CanvasWidget::renderTriangles(Vec2F const& renderingOffset, List<tuple<Vec2
|
||||
void CanvasWidget::renderText(Vec2F const& renderingOffset, String const& s, TextPositioning const& position, unsigned fontSize, Vec4B const& color, FontMode mode, float lineSpacing, String const& font, String const& directives) {
|
||||
auto& context = GuiContext::singleton();
|
||||
context.setFontProcessingDirectives(directives);
|
||||
context.setFontSize(fontSize);
|
||||
context.setFontSize(fontSize, m_ignoreInterfaceScale ? 1 : context.interfaceScale());
|
||||
context.setFontColor(color);
|
||||
context.setFontMode(mode);
|
||||
context.setFont(font);
|
||||
|
@ -98,9 +98,9 @@ void GuiContext::setInterfaceScale(int interfaceScale) {
|
||||
m_interfaceScale = interfaceScale;
|
||||
}
|
||||
|
||||
Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event) const {
|
||||
auto getInterfacePosition = [this](Vec2I pos) {
|
||||
return Vec2I(pos) / interfaceScale();
|
||||
Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event, int pixelRatio) const {
|
||||
auto getInterfacePosition = [pixelRatio](Vec2I pos) {
|
||||
return Vec2I(pos) / pixelRatio;
|
||||
};
|
||||
|
||||
if (auto mouseMoveEvent = event.ptr<MouseMoveEvent>())
|
||||
@ -115,6 +115,10 @@ Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event) const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<Vec2I> GuiContext::mousePosition(InputEvent const& event) const {
|
||||
return mousePosition(event, interfaceScale());
|
||||
}
|
||||
|
||||
Set<InterfaceAction> GuiContext::actions(InputEvent const& event) const {
|
||||
return m_keyBindings.actions(event);
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ public:
|
||||
int interfaceScale() const;
|
||||
void setInterfaceScale(int interfaceScale);
|
||||
|
||||
Maybe<Vec2I> mousePosition(InputEvent const& event, int pixelRatio) const;
|
||||
Maybe<Vec2I> mousePosition(InputEvent const& event) const;
|
||||
|
||||
Set<InterfaceAction> actions(InputEvent const& event) const;
|
||||
|
@ -404,6 +404,10 @@ LuaCallbacks Pane::makePaneCallbacks() {
|
||||
return this->removeChild(widgetName);
|
||||
});
|
||||
|
||||
callbacks.registerCallback("scale", []() -> int {
|
||||
return GuiContext::singleton().interfaceScale();
|
||||
});
|
||||
|
||||
return callbacks;
|
||||
}
|
||||
|
||||
|