Skip to content

Commit

Permalink
Userscript Security + Loading Screen Changes (#33)
Browse files Browse the repository at this point in the history
  • Loading branch information
AspectQuote authored Oct 13, 2024
1 parent 941097b commit 309d92b
Show file tree
Hide file tree
Showing 13 changed files with 540 additions and 152 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
dist/
app/
node_modules/
package-lock.json
multiselect_dummy.html
package-lock.json
30 changes: 18 additions & 12 deletions USERSCRIPTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ return this;
Beginning in version 1.9.0, crankshaft supports the ability for scripts to **easily** inject their own custom settings into Krunker, allowing for userscript creators to implement easy-to-change settings into the client.
![Custom crankshaft userscript settings](./assets/customsettings_screenshot.png)

Doing this is very easy! All you have to do is include a `.settings` property on your script's root `this` object!
Doing this is very easy, all you have to do is include a `settings` attribute on your script's root `this` property.

```js
// ==UserScript==
Expand All @@ -236,17 +236,17 @@ this.settings = {}
return this // Make sure you include this part!
```

That's all you have to do! Obviously though, it's not super useful to have no custom settings. Thankfully, it's simple to implement plenty of custom settings. Let's walk through adding a custom setting step-by-step.
Obviously, it's not super useful to have no custom settings. It's simple to populate this object with custom settings. Let's walk through adding a custom setting step-by-step.

#### Step 1 - Add the setting key:
```js
this.settings = {
"mySuperAwesomeCustomSetting": {
"mySuperAwesomeCustomSetting": { /* NEW */
/* Setting properties go here */
}
}
```
When you add a property to the `settings` object, crankshaft will recognize it as a setting it needs to display. This key will be used to store the setting's value when it's saved to disk. This can be anything, but follow standard JavaScript object property guidelines. (No spaces or special characters.)
When adding a property to the `settings` attribute, crankshaft will recognize it as a setting it needs to display. This key will be used to store the setting's value when it's saved to disk. This can be anything, but it's best practice to follow standard JavaScript object property guidelines. (No spaces or special characters.)

#### Step 2 - Add the required setting properties:
```js
Expand All @@ -263,9 +263,12 @@ After you add the properties to your custom setting, crankshaft will know what k
Custom Setting Required Schematic
- `mySuperAwesomeCustomSetting` Setting key. Explained above in Step 1.
- `title`: _string_ - This will be what the setting name is. (i.e. "Default Region", "Render Distance")
- `type`: _string_ - The type of setting the setting is. Can be 'bool' (boolean), 'num' (number), 'sel' (selection), or 'color' (color).
- `type`: _string_ - The type of setting the setting is. Can be `bool` (boolean), `num` (number), 'sel' (selection), or `color` (HEX color string).
- `value`: _boolean|number|string_ - The default value for the setting. The type of this must match the `type` property.

Custom Setting Conditional Required Schematic
- `opts`: _array_ - The options for a particular setting. Only works with the `sel` (selection) setting type. NOTE: This is REQUIRED and needs at least 2 options if the type of setting is `sel`.

#### Step 3 - Add the optional setting properties:
```js
this.settings = {
Expand All @@ -277,15 +280,15 @@ this.settings = {
}
}
```
After adding the required properties, you might want to add some of the optional properties! Optional properties can control some of the behaviours of settings, depending on what `type` of setting it is!
After adding the required properties, it is often useful to add some optional properties. Optional properties control some of the behaviours of settings, depending on what `type` of setting it is.

Custom Setting Optional Schematic
- `mySuperAwesomeCustomSetting` Setting key. Explained above in Step 1.
- `desc`: _string_ - The setting's description. This will be displayed in small text below the setting `title`.
- `min`: _number_ - The setting's minimum value. This only works with `num` type settings. Defaults to `0`.
- `max`: _number_ - The setting's maximum value. This only works with `num` type settings. Defaults to `100`.
- `step`: _number_ - The setting's step value. This only works with `num` type settings. This controls how many times each slider tick increases the setting value. Must be smaller than the difference between the `min` and `max` value if they are set. (Play with this, it's hard to explain, but fairly simple in practice.)
- `opts`: _array_ - The options for a particular setting. Only works with the `sel` (selection) setting type.
- `step`: _number_ - The setting's step value. This only works with `num` type settings. This controls how many times each slider tick increases the setting value. Must be smaller than the difference between the `min` and `max` value if they are set. (Play with this; it's hard to explain, but fairly simple in practice.)


#### Step 4 - Add the changed() function:
```js
Expand All @@ -299,9 +302,9 @@ this.settings = {
}
}
```
This is where the magic happens! Whenever your custom setting is changed, it will automatically call the changed() function, allowing your script to do what it needs to with the new value.
Whenever a custom setting is changed, it will automatically call the changed() function with the new value as the argument, allowing the script to do what it needs to with the new value.

For this, **you should use an ES6 arrow function** to be sure your script retains its own [scope](https://developer.mozilla.org/en-US/docs/Glossary/Scope). However, bind()-ing the top-level `this` keyword to the function like the code below works to retain the top-level `this` of your script, but nothing else:
For this, **you should use an ES6 arrow function** to be sure the script retains its own [scope](https://developer.mozilla.org/en-US/docs/Glossary/Scope). However, bind()-ing the top-level `this` keyword to the function like the code below works to retain the top-level `this` of the script, but nothing else:
```js
this.settings = {
"mySuperAwesomeCustomSetting": {
Expand All @@ -314,7 +317,8 @@ this.settings = {
}
```

After that, you're all set! You've successfully added a custom setting to your krunker script, and can use the value however you like. In order to add more settings, simply create another key and define the properties you need!
With this, the custom setting has been successfully added to the script, and is able to use the value however you like.
To add more settings, simply create another key and define more setting properties.
```js
this.settings = {
"mySuperAwesomeCustomSetting": {
Expand All @@ -331,14 +335,16 @@ this.settings = {
"value": 9001,
"min": 9000,
"max": 100000,
"step": 10,
"step": 1,
changed: (value) => { this._console.log(value) }
}
}
```
![Finished custom settings example](./assets/customsettings_finishedexample.png)

Custom Settings Implementation Examples:
[customsettingsexample.js](./assets/userscriptexamples/customsettingsexample.js)
[customcsschanger.js](./assets/userscriptexamples/customcsschanger.js)
[keystrokes.js](https://github.com/AspectQuote/krunkeruserscripts/blob/main/keystrokes.js)

## Tips / Notes
Expand Down
25 changes: 24 additions & 1 deletion assets/menuTimer.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
top: 0;
}

#uiBase.onMenu #spectateUI > div {
#uiBase.onMenu #spectateUI > #spectateHUD {
z-index: 1;
transform: unset;
}
Expand All @@ -42,4 +42,27 @@
padding: 25px;
font-size: 42px;
border-radius: 0.5em;
}

/* For some reason, the game insists on showing KPD controls when first loading in. (it doesn't do this on browser so I don't know why it happens) */
#uiBase.onMenu #specKPDContr {
display: none;
}

#uiBase.onMenu #spectateUI div#specStats {
position: absolute;
top: calc(50% + 13em);
left: 50%;
transform: translateX(-50%);
z-index: 1;
}

#uiBase.onMenu #spectateUI div#specStats:before {
content: 'Spectating';
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
font-size: 1.2em;
padding-bottom: 0.5em;
}
64 changes: 58 additions & 6 deletions assets/splashCss.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#loadEditrBtn,
#loadInfoLHolder,
#loadInfoRHolder {
display: none !important;
}

html {
background: #363636 !important;
}
Expand All @@ -13,6 +7,7 @@ html {
background-size: 100px !important;
background-repeat: repeat !important;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAMAAAC8EZcfAAAAclBMVEXE/+IAAAAoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgnJycoKCgoKCgoKCgoKCgnJycoKCgoKCgnJycoKCgoKCgoKCgoKCgoKCgpKSm+1W+6AAAAAXRSTlMAQObYZgAAAfpJREFUeNrt3QGqIjsQRuHD2URW0Pvf4oPHcMMt0j0OXqx/hjoAggX4AdJJbEX+zx3gTj7e+cW9CYKF6Gddfyjks+kLwmbfWbgl3b6zEKuw03cQYoBQ74V4FpIixKMQUoQUUL+vCLEKu31FiFXY7KvCPaUrfRLiV90+jkJsFv5+n+eu1xcorL6z0J12ATlpGn1VCPdCA96DcCuU4gvIb7EfSOkABCSnCpS0CpC8CjBZiOFCVCLbQEgWIkQLSS4eSLdvmqZpmqZpmqZpTj5zuH0z44HZQr+K5SUTLQ3wTWGkI4FXgR6Gi4cGaOmHl7oBon2udQtcKq1d3ndtal++UrrPdJ/GA0335QNN8S0ArlTgkz3At2B33QDXSll8r+NU5XOtQnjy9+xpnnycfX3C62lYnughUiv2buALo/XgiwDC1fkTEGqnkbqafFK6TjOVyMvM4lcr5qSOR2Gjz+txGgDUpyFRwmUoUAVPzZb/TeCci+/6R4CQLVw0tu4vLnCpCR8Qroe17XXf3FBafxlwEdfcM/xR4WKapmmapmmapuDmW8XvZTjQcKDBQNN9mu5zfOPrK9Q3whEGZLhQs4WqYpqweDBV6BcwU+gGZgsxXIipQjcwUmgBBgnL+cg4od/CNGHhYZaw+IAc4dl31riz3QePZsP+ueLg6xX+B4FEGkmLcq6yAAAAAElFTkSuQmCC) !important;
box-shadow: 0.7em 0.7em 0 rgba(0, 0, 0, 0.5);

position: absolute;
left: 50%;
Expand All @@ -27,3 +22,60 @@ html {
height: min-content;
}

/* floating toasts css that is required */
.crankshaft-holder-l,
.crankshaft-holder-r,
.crankshaft-holder-update,
.crankshaft-holder-splash,
.crankshaft-holder-loadingindicator {
position: absolute;
font-size: 20px !important;
color: rgba(255, 255, 255, 0.7);
display: block !important;
}

.crankshaft-holder-r {
right: 20px;
bottom: 20px;
text-align: right;
}

.crankshaft-holder-l {
left: 20px;
bottom: 20px;
text-align: left;
}

.crankshaft-holder-update {
top: 20px;
left: 50% !important;
background-color: black;
padding: 1rem;
border-radius: 0.5rem;
width: max-content;
z-index: 10;
}

.crankshaft-holder-splash {
top: 100%;
left: 0%;
margin: 0.6em 1em;
}
.crankshaft-holder-loadingindicator {
bottom: 100%;
right: 0%;
margin: 0.6em 1em;
}

.crankshaft-loading-background {
width: 100%;
height: 100%;
z-index: 50;
position: absolute;
top: 0;
left: 0;
}
.crankshaft-loading-background.immersive {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAMAAAC8EZcfAAAAclBMVEXE/+IAAAAoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgnJycoKCgoKCgoKCgoKCgnJycoKCgoKCgnJycoKCgoKCgoKCgoKCgoKCgpKSm+1W+6AAAAAXRSTlMAQObYZgAAAfpJREFUeNrt3QGqIjsQRuHD2URW0Pvf4oPHcMMt0j0OXqx/hjoAggX4AdJJbEX+zx3gTj7e+cW9CYKF6Gddfyjks+kLwmbfWbgl3b6zEKuw03cQYoBQ74V4FpIixKMQUoQUUL+vCLEKu31FiFXY7KvCPaUrfRLiV90+jkJsFv5+n+eu1xcorL6z0J12ATlpGn1VCPdCA96DcCuU4gvIb7EfSOkABCSnCpS0CpC8CjBZiOFCVCLbQEgWIkQLSS4eSLdvmqZpmqZpmqZpTj5zuH0z44HZQr+K5SUTLQ3wTWGkI4FXgR6Gi4cGaOmHl7oBon2udQtcKq1d3ndtal++UrrPdJ/GA0335QNN8S0ArlTgkz3At2B33QDXSll8r+NU5XOtQnjy9+xpnnycfX3C62lYnughUiv2buALo/XgiwDC1fkTEGqnkbqafFK6TjOVyMvM4lcr5qSOR2Gjz+txGgDUpyFRwmUoUAVPzZb/TeCci+/6R4CQLVw0tu4vLnCpCR8Qroe17XXf3FBafxlwEdfcM/xR4WKapmmapmmapuDmW8XvZTjQcKDBQNN9mu5zfOPrK9Q3whEGZLhQs4WqYpqweDBV6BcwU+gGZgsxXIipQjcwUmgBBgnL+cg4od/CNGHhYZaw+IAc4dl31riz3QePZsP+ueLg6xX+B4FEGkmLcq6yAAAAAElFTkSuQmCC);
background-color: #171717;
}
58 changes: 58 additions & 0 deletions assets/userscriptexamples/customcsschanger.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// ==UserScript==
// @name Example Custom CSS Changer
// @author AspectQuote
// @version 1
// @desc Uses custom settings to change custom CSS.
// ==/UserScript==

let currentCSS = -1;
const customCSSes = [
{
name: "No CSS",
css: ``
},
{
name: "Hide Bottom Right Info Bar",
css: `#mapInfoHolder {
display: none !important;
}`
},
{
name: "Hide Krunker Logo",
css: `#gameNameHolder, #seasonLabel {
display: none !important;
}`
}
]

removeCSS = () => {
this._css('', 'customcssindex' + currentCSS, false);
}

swapCSS = (cssIndex) => {
removeCSS();
if (cssIndex !== currentCSS) {
this._css(customCSSes[cssIndex].css, 'customcssindex'+cssIndex, true);
}
currentCSS = cssIndex;
}

// remove the css when userscript is unloaded
this.unload = () => {
removeCSS();
}

this.settings = {
'usingcss': {
title: "CSS to inject",
type: 'sel',
desc: "The custom CSS you want to use.",
value: customCSSes[0].name,
opts: customCSSes.map(item => item.name),
changed: (newName) => {
swapCSS(customCSSes.findIndex(item => item.name === newName));
}
}
}

return this;
60 changes: 60 additions & 0 deletions assets/userscriptexamples/customsettingsexample.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// ==UserScript==
// @name Example Custom Settings Script
// @author AspectQuote
// @version 1
// @desc Example custom settings script.
// ==/UserScript==

this.settings = {
"customSetting1": {
"title": "Custom Boolean Setting",
"desc": "A boolean option, can be flipped 'on' (true) or 'off' (false).",
"type": "bool",
"value": true,
changed: function (value) { this._console.log(value) } // This will throw an error, as declaring a function as a block changes the scope of the function, thus we cannot use console.log
},
"customSetting2": {
"title": "Custom Color Setting",
"desc": "Custom color option, defaults to red. (#ff0000)",
"type": "color",
"value": "#ff0000",
changed: (value) => { this._console.log(value) }
},
"customSetting3": {
"title": "Custom Selection Setting",
"desc": "Custom selection option, includes 3 options to pick from.",
"type": "sel",
"opts": ["ExampleOption1", "ExampleOption2", "ExampleOption3"],
"value": "ExampleOption1",
changed: (value) => { this._console.log(value) }
},
"customSetting4": {
"title": "Custom Number Setting (With optional properties)",
"desc": "Number option with optional properties.",
type: 'num',
min: 0,
max: 10,
step: 0.1,
"value": 5,
changed: (value) => { this._console.log(value) }
},
"customSetting5": {
"title": "Custom Number Setting (Without optional properties)",
type: 'num',
"value": 1000,
changed: (value) => { this._console.log(value) }
},
"brokenCustomSettingExample": {
"title": "Broken Setting",
"desc": "This is meant to be broken.",
type: 'num',
min: 0,
max: 10,
step: 0.1,
"value": "thisStringBreaksThisSetting",
changed: (value) => { this._console.log(value) }
},
"brokenCustomSettingExample2": {}
}

return this
5 changes: 3 additions & 2 deletions esbuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const buildLogger = {
}

const buildOptions = {
// keep this manually in-sync! THANKS FOR LETTING ME KNOW!
// keep this manually in-sync!
entryPoints: [
'src/main.ts',
'src/menu.ts',
Expand All @@ -26,7 +26,8 @@ const buildOptions = {
'src/matchmaker.ts',
'src/utils_node.ts',
'src/utils.ts',
'src/userscriptvalidators.ts'
'src/userscriptvalidators.ts',
'src/splashscreen.ts'
],
bundle: false,
minify: building,
Expand Down
3 changes: 2 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ const settingsSkeleton = {
resourceSwapper: true,
userscripts: false,
clientSplash: true,
immersiveSplash: false,
discordRPC: false,
extendedRPC: true,
'angle-backend': 'default',
Expand Down Expand Up @@ -473,4 +474,4 @@ app.on('ready', () => {
// eslint-disable-next-line consistent-return
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') return app.quit(); // don't quit on mac systems unless user explicitly quits
});
});
Loading

0 comments on commit 309d92b

Please sign in to comment.