com.robotsquid.squidhub 0.5.7 → 0.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/Editor/SquidHubBuildScan.cs +45 -0
- package/Editor/SquidHubHtml.cs +95 -1
- package/Editor/SquidHubUploader.cs +40 -20
- package/README.md +17 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,57 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.9
|
|
4
|
+
|
|
5
|
+
**Build and Upload just uploads.** The "Upload N files (X MB)?" confirmation is
|
|
6
|
+
gone. Choosing the action IS the decision, and a modal that appears after a long
|
|
7
|
+
WebGL build is worse than redundant — it stops an unattended build from ever
|
|
8
|
+
finishing.
|
|
9
|
+
|
|
10
|
+
Nothing it said is lost, only moved. The file count, the size, the endpoint, the
|
|
11
|
+
skipped OS metadata, the missing-manifest warning and the build warnings all go
|
|
12
|
+
to the Console, where they can be read *during* the upload and are still there
|
|
13
|
+
afterwards. A dialog is gone the moment it is dismissed, which is exactly when a
|
|
14
|
+
warning stops being read.
|
|
15
|
+
|
|
16
|
+
⚠️ **Refusals still stop the run.** A precompressed build or one with no
|
|
17
|
+
`index.html` at its top level is a failure, not a confirmation, and the server
|
|
18
|
+
would reject it anyway.
|
|
19
|
+
|
|
20
|
+
## 0.5.8
|
|
21
|
+
|
|
22
|
+
**The game fills the frame, and Unity's chrome is gone.**
|
|
23
|
+
|
|
24
|
+
The portal already does the letterboxing: `game-frame.tsx` measures its box,
|
|
25
|
+
clamps it to the aspect window in `manifest.json` and hands the iframe an
|
|
26
|
+
element of exactly that size. Unity's Default template then rendered a fixed
|
|
27
|
+
960×600 box centred inside that — letterboxing a second time, at the wrong size,
|
|
28
|
+
on a white page, under a Unity logo, a build title and a second fullscreen
|
|
29
|
+
button. Measured in a browser at a 1555×1290 frame: the canvas was 960×600 at
|
|
30
|
+
offset (298, 326), using 29% of the area it was given.
|
|
31
|
+
|
|
32
|
+
The post-build step now injects a stylesheet that fills the frame and hides
|
|
33
|
+
`#unity-footer`. Verified in Chrome on a real build: the canvas fills the
|
|
34
|
+
viewport exactly, no scrollbars, and the WebGL render target follows at
|
|
35
|
+
3110×2580 for devicePixelRatio 2 — full resolution, not an upscale.
|
|
36
|
+
|
|
37
|
+
⚠️ **The footer is HIDDEN, never removed.** The stock bootstrap assigns
|
|
38
|
+
`document.querySelector("#unity-fullscreen-button").onclick` inside the promise
|
|
39
|
+
that resolves the instance. Delete the element and that throws, the chain falls
|
|
40
|
+
into `.catch(alert)`, and the player gets a modal dialog instead of a game.
|
|
41
|
+
|
|
42
|
+
⚠️ **The loading bar stays.** The portal draws no loading UI of its own, so it
|
|
43
|
+
is the only progress a player sees while a 30 MB build downloads.
|
|
44
|
+
|
|
45
|
+
⚠️ **The engine splash screen is untouched.** "Made with Unity" is a licence
|
|
46
|
+
term of Unity Personal and is not template chrome; this strips the template's
|
|
47
|
+
own logo bar, nothing else.
|
|
48
|
+
|
|
49
|
+
**Uploading an older build now warns rather than staying quiet** — one warning
|
|
50
|
+
for a build with no bridge at all, another for a bridged build made before frame
|
|
51
|
+
fitting. Neither refuses: both upload and register perfectly, they just play
|
|
52
|
+
badly, and the fix is a rebuild. It cannot be repaired at upload time — writing
|
|
53
|
+
a bridge tag for a file that is not in the folder trades the handshake for a 404.
|
|
54
|
+
|
|
3
55
|
## 0.5.7
|
|
4
56
|
|
|
5
57
|
**The manifest is a form now, and a build can go out in one action.**
|
|
@@ -29,6 +29,8 @@ namespace SquidHub.Editor
|
|
|
29
29
|
public readonly List<string> Refusals = new List<string>();
|
|
30
30
|
/// <summary>OS metadata dropped. Reported, never silent.</summary>
|
|
31
31
|
public readonly List<string> Skipped = new List<string>();
|
|
32
|
+
/// <summary>Things wrong with the build that do not justify refusing it.</summary>
|
|
33
|
+
public readonly List<string> Warnings = new List<string>();
|
|
32
34
|
public long TotalBytes;
|
|
33
35
|
public bool HasEntryPoint;
|
|
34
36
|
}
|
|
@@ -111,9 +113,52 @@ namespace SquidHub.Editor
|
|
|
111
113
|
"Point this at the folder Unity built into, not at its parent.");
|
|
112
114
|
}
|
|
113
115
|
|
|
116
|
+
InspectEntryPoint(scan);
|
|
114
117
|
return scan;
|
|
115
118
|
}
|
|
116
119
|
|
|
120
|
+
/// <summary>
|
|
121
|
+
/// What the exported index.html says about how it was built.
|
|
122
|
+
///
|
|
123
|
+
/// ⚠️ WARNINGS, NEVER REFUSALS. Both of these describe a build that
|
|
124
|
+
/// uploads and registers perfectly — one just plays badly, and the other
|
|
125
|
+
/// is somebody's custom pipeline we have no business overruling from
|
|
126
|
+
/// here. The refusals above are for builds the SERVER would reject; this
|
|
127
|
+
/// is for builds the PLAYER would notice.
|
|
128
|
+
///
|
|
129
|
+
/// ⚠️ And it cannot be fixed at upload time, which is why it only warns.
|
|
130
|
+
/// Injecting the bridge tag here would name a squidhub-bridge.js that is
|
|
131
|
+
/// not in the folder — a 404 in place of the handshake, which is worse
|
|
132
|
+
/// than the problem it set out to solve. The fix is a rebuild.
|
|
133
|
+
/// </summary>
|
|
134
|
+
private static void InspectEntryPoint(BuildScan scan)
|
|
135
|
+
{
|
|
136
|
+
var entry = scan.Files.FirstOrDefault(f => f.Path == "index.html");
|
|
137
|
+
if (entry == null) return;
|
|
138
|
+
|
|
139
|
+
string html;
|
|
140
|
+
try { html = File.ReadAllText(entry.FullPath); }
|
|
141
|
+
catch { return; }
|
|
142
|
+
|
|
143
|
+
if (html.IndexOf("squidhub-bridge.js", StringComparison.OrdinalIgnoreCase) < 0)
|
|
144
|
+
{
|
|
145
|
+
scan.Warnings.Add(
|
|
146
|
+
"index.html does not reference squidhub-bridge.js, so this build was not made " +
|
|
147
|
+
"with the SquidHub package installed. There is no window.SquidHub and every " +
|
|
148
|
+
"handshake will time out. Build again with the package in the project.");
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (html.IndexOf(SquidHubHtml.FitStyleId, StringComparison.OrdinalIgnoreCase) < 0)
|
|
153
|
+
{
|
|
154
|
+
scan.Warnings.Add(
|
|
155
|
+
"This build predates frame fitting, so Unity's template chrome is still in it: " +
|
|
156
|
+
"the game renders in a fixed 960x600 box centred in whatever frame the portal " +
|
|
157
|
+
"gives it, on a white page, under a Unity logo and a second fullscreen button. " +
|
|
158
|
+
"Rebuild to fill the frame.");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
117
162
|
private static string ToHex(byte[] bytes)
|
|
118
163
|
{
|
|
119
164
|
var chars = new char[bytes.Length * 2];
|
package/Editor/SquidHubHtml.cs
CHANGED
|
@@ -49,6 +49,66 @@ namespace SquidHub.Editor
|
|
|
49
49
|
"text/ecmascript", "application/ecmascript",
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
+
/// <summary>Marks our stylesheet, so a reprocess can recognise its own work.</summary>
|
|
53
|
+
internal const string FitStyleId = "squidhub-fit";
|
|
54
|
+
|
|
55
|
+
/// <summary>
|
|
56
|
+
/// Makes the game fill the frame the portal gives it, and hides the
|
|
57
|
+
/// template's own chrome.
|
|
58
|
+
///
|
|
59
|
+
/// ⚠️ THE PORTAL ALREADY DID THE LETTERBOXING. `game-frame.tsx` measures
|
|
60
|
+
/// its box, clamps it to the aspect window declared in manifest.json and
|
|
61
|
+
/// hands the iframe an element of exactly that size with black bars
|
|
62
|
+
/// around it. Unity's Default template then renders a fixed 960×600 box
|
|
63
|
+
/// centred inside that — letterboxing a second time, at the wrong size,
|
|
64
|
+
/// so a 4K monitor gets a postage stamp. Filling is not a preference
|
|
65
|
+
/// here; the frame is already the right shape.
|
|
66
|
+
///
|
|
67
|
+
/// ⚠️ `!important` is doing real work. The template's bootstrap sets
|
|
68
|
+
/// `fullscreenContainer.style.width = "960px"` as an INLINE style, and
|
|
69
|
+
/// the Minimal template writes inline px on the canvas itself. A plain
|
|
70
|
+
/// rule loses to both.
|
|
71
|
+
///
|
|
72
|
+
/// ⚠️ `#unity-footer` is HIDDEN, never removed. The stock bootstrap does
|
|
73
|
+
/// `document.querySelector("#unity-fullscreen-button").onclick = …`
|
|
74
|
+
/// inside the promise that resolves the instance; delete the element and
|
|
75
|
+
/// that throws, the chain falls into `.catch(alert)`, and the player gets
|
|
76
|
+
/// a modal dialog instead of a game. Hiding leaves every query answerable.
|
|
77
|
+
///
|
|
78
|
+
/// ⚠️ The loading bar STAYS. The portal draws no loading UI of its own,
|
|
79
|
+
/// so this is the only progress a player sees while a 30 MB build
|
|
80
|
+
/// downloads — hiding it would trade a Unity logo for a black screen.
|
|
81
|
+
///
|
|
82
|
+
/// ⚠️ An inline <style> is safe where an inline script is not:
|
|
83
|
+
/// gamehost sends `style-src 'self' 'unsafe-inline'` but no
|
|
84
|
+
/// `'unsafe-inline'` for scripts. That asymmetry is why this is a style
|
|
85
|
+
/// block and the bootstrap had to be hoisted to a file.
|
|
86
|
+
/// </summary>
|
|
87
|
+
private static readonly string FitStyle =
|
|
88
|
+
"<style id=\"" + FitStyleId + "\">\n" +
|
|
89
|
+
" /* SquidHub: the portal sizes and letterboxes the frame; the game fills it. */\n" +
|
|
90
|
+
" html, body { width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; }\n" +
|
|
91
|
+
" #unity-container,\n" +
|
|
92
|
+
" #unity-container.unity-desktop,\n" +
|
|
93
|
+
" #unity-container.unity-mobile {\n" +
|
|
94
|
+
" position: absolute; left: 0; top: 0;\n" +
|
|
95
|
+
" width: 100%; height: 100%; transform: none;\n" +
|
|
96
|
+
" }\n" +
|
|
97
|
+
" #unity-fullscreen-container {\n" +
|
|
98
|
+
" position: absolute; left: 0; top: 0;\n" +
|
|
99
|
+
" width: 100% !important; height: 100% !important;\n" +
|
|
100
|
+
" }\n" +
|
|
101
|
+
" #unity-canvas {\n" +
|
|
102
|
+
" display: block; left: 0; top: 0;\n" +
|
|
103
|
+
" width: 100% !important; height: 100% !important;\n" +
|
|
104
|
+
" }\n" +
|
|
105
|
+
" /* Unity\u0027s own logo, build title and fullscreen button. The portal\n" +
|
|
106
|
+
" draws its own fullscreen control from display.fullscreen, and two\n" +
|
|
107
|
+
" of them fight. Hidden rather than removed — the bootstrap still\n" +
|
|
108
|
+
" queries the button and would throw on null. */\n" +
|
|
109
|
+
" #unity-footer { display: none !important; }\n" +
|
|
110
|
+
" </style>\n";
|
|
111
|
+
|
|
52
112
|
public static Result Apply(string html, string bridgeFileName)
|
|
53
113
|
{
|
|
54
114
|
var result = new Result { Html = html };
|
|
@@ -83,7 +143,7 @@ namespace SquidHub.Editor
|
|
|
83
143
|
});
|
|
84
144
|
|
|
85
145
|
var tag = "<script src=\"" + bridgeFileName + "\"></script>";
|
|
86
|
-
result.Html = InsertBridge(rewritten, tag, result);
|
|
146
|
+
result.Html = InsertFitStyle(InsertBridge(rewritten, tag, result), result);
|
|
87
147
|
result.Changed = !ReferenceEquals(result.Html, html) && result.Html != html;
|
|
88
148
|
return result;
|
|
89
149
|
}
|
|
@@ -98,6 +158,40 @@ namespace SquidHub.Editor
|
|
|
98
158
|
return ExecutableTypes.Contains(value.Trim());
|
|
99
159
|
}
|
|
100
160
|
|
|
161
|
+
/// <summary>
|
|
162
|
+
/// Puts the stylesheet last in <head>, so it follows the template's own
|
|
163
|
+
/// `TemplateData/style.css` and wins on order as well as on specificity.
|
|
164
|
+
/// </summary>
|
|
165
|
+
private static string InsertFitStyle(string html, Result result)
|
|
166
|
+
{
|
|
167
|
+
// Somebody's custom template may already carry it; do not stack a second.
|
|
168
|
+
if (html.IndexOf(FitStyleId, StringComparison.OrdinalIgnoreCase) >= 0)
|
|
169
|
+
{
|
|
170
|
+
result.Notes.Add("fit stylesheet already present — left alone");
|
|
171
|
+
return html;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
var at = html.IndexOf("</head>", StringComparison.OrdinalIgnoreCase);
|
|
175
|
+
if (at >= 0)
|
|
176
|
+
{
|
|
177
|
+
result.Notes.Add("canvas set to fill the frame, Unity footer hidden");
|
|
178
|
+
return html.Substring(0, at) + " " + FitStyle + html.Substring(at);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ⚠️ A document with no </head> still gets it, before the first tag
|
|
182
|
+
// that could render. An unstyled page here is a game the player sees
|
|
183
|
+
// at 960×600 in the middle of a 4K frame, which reads as broken.
|
|
184
|
+
at = html.IndexOf("<body", StringComparison.OrdinalIgnoreCase);
|
|
185
|
+
if (at >= 0)
|
|
186
|
+
{
|
|
187
|
+
result.Notes.Add("no </head> — fit stylesheet injected before <body>");
|
|
188
|
+
return html.Substring(0, at) + FitStyle + html.Substring(at);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
result.Notes.Add("no </head> or <body> — fit stylesheet prepended");
|
|
192
|
+
return FitStyle + html;
|
|
193
|
+
}
|
|
194
|
+
|
|
101
195
|
// ⚠️ Before the FIRST script in the document, not merely before the
|
|
102
196
|
// loader. installBridge() patches AudioContext.destination on the
|
|
103
197
|
// instance, so it only catches contexts created after it runs — land
|
|
@@ -85,29 +85,49 @@ namespace SquidHub.Editor
|
|
|
85
85
|
|
|
86
86
|
var publish = SquidHubUploadSettings.PublishAfterUpload;
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
88
|
+
// ⚠️ NO CONFIRMATION. Choosing "Build and Upload" IS the decision;
|
|
89
|
+
// asking again is a modal between the publisher and the thing they
|
|
90
|
+
// just asked for, and one that appears after a long WebGL build is
|
|
91
|
+
// worse than useless — it stops an unattended build from finishing.
|
|
92
|
+
//
|
|
93
|
+
// ⚠️ Nothing is lost, only moved. Everything the dialog used to say
|
|
94
|
+
// goes to the Console, where it can be read during the upload and is
|
|
95
|
+
// still there afterwards — a dialog is gone the moment it is
|
|
96
|
+
// dismissed, which is exactly when a warning stops being read.
|
|
97
|
+
// Refusals above still stop the run; those are failures, not
|
|
98
|
+
// confirmations.
|
|
99
|
+
Report(scan, publish);
|
|
100
|
+
|
|
101
|
+
Run(Upload(scan, publish));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/// <summary>
|
|
105
|
+
/// What the dialog used to ask, said to the Console instead.
|
|
106
|
+
///
|
|
107
|
+
/// ⚠️ Warnings are LogWarning, not Log. They describe a build that
|
|
108
|
+
/// uploads perfectly and plays badly, so they have to survive a Console
|
|
109
|
+
/// that is already full of build spam.
|
|
110
|
+
/// </summary>
|
|
111
|
+
private static void Report(BuildScan scan, bool publish)
|
|
112
|
+
{
|
|
113
|
+
Debug.Log($"[SquidHub] uploading {scan.Files.Count} file(s), " +
|
|
114
|
+
$"{scan.TotalBytes / 1024f / 1024f:F1} MB, to {SquidHubUploadSettings.Endpoint}" +
|
|
115
|
+
(scan.Skipped.Count > 0
|
|
116
|
+
? $" ({scan.Skipped.Count} OS metadata file(s) skipped)"
|
|
117
|
+
: "") +
|
|
118
|
+
(publish ? " — will publish on success where the server allows it (dev only)." : "."));
|
|
119
|
+
|
|
120
|
+
// ⚠️ A build folder with no manifest.json declares nothing — save
|
|
121
|
+
// schema 1, no display constraints — and nothing in the upload
|
|
122
|
+
// result would ever tell the publisher that.
|
|
123
|
+
if (!scan.Files.Any(f => f.Path == "manifest.json"))
|
|
106
124
|
{
|
|
107
|
-
|
|
125
|
+
Debug.LogWarning(
|
|
126
|
+
"[SquidHub] no manifest.json in this build, so it declares nothing: save schema 1 " +
|
|
127
|
+
"and no display constraints. Set one up in SquidHub → Game Manifest… and rebuild.");
|
|
108
128
|
}
|
|
109
129
|
|
|
110
|
-
|
|
130
|
+
foreach (var warning in scan.Warnings) Debug.LogWarning("[SquidHub] " + warning);
|
|
111
131
|
}
|
|
112
132
|
|
|
113
133
|
// ⚠️ Driven by EditorApplication.update rather than blocking. Sleeping
|
package/README.md
CHANGED
|
@@ -68,6 +68,8 @@ Nothing to configure. On a WebGL build this package:
|
|
|
68
68
|
2. **Writes `squidhub-bridge.js`** into the output and injects it ahead of the
|
|
69
69
|
Unity loader.
|
|
70
70
|
3. **Hoists every inline `<script>`** out of `index.html` into an external file.
|
|
71
|
+
4. **Makes the game fill the frame** and hides Unity's own logo bar, build title
|
|
72
|
+
and fullscreen button.
|
|
71
73
|
|
|
72
74
|
Step 3 is the one that matters. `squidhub-gamehost` serves
|
|
73
75
|
`script-src 'self' 'wasm-unsafe-eval' blob:` with **no `'unsafe-inline'`**, and
|
|
@@ -76,6 +78,16 @@ all the markup is there, and nothing runs — the only signal is a CSP violation
|
|
|
76
78
|
in the console. This is the single most common first-integration failure, and
|
|
77
79
|
you should never see it.
|
|
78
80
|
|
|
81
|
+
⚠️ **Step 4 is why your game is not a 960×600 box in the corner.** The portal
|
|
82
|
+
already letterboxes — it measures its box, clamps it to the aspect window you
|
|
83
|
+
declared and hands the frame an element of exactly that size. Unity's stock
|
|
84
|
+
template then renders a fixed 960×600 centred inside *that*, on a white page,
|
|
85
|
+
under a Unity logo and a second fullscreen button that fights the portal's own.
|
|
86
|
+
The footer is hidden rather than removed, because the stock bootstrap queries
|
|
87
|
+
the fullscreen button and throws on null — which surfaces as an `alert()` where
|
|
88
|
+
the game should be. Your loading bar is left alone: the portal draws none, so it
|
|
89
|
+
is the only progress a player sees.
|
|
90
|
+
|
|
79
91
|
Then push the folder:
|
|
80
92
|
|
|
81
93
|
```sh
|
|
@@ -132,6 +144,11 @@ an upload key is not a person — it sits in CI configs and Editor preferences.
|
|
|
132
144
|
That asymmetry is deliberate: it is what keeps a leaked key from replacing the
|
|
133
145
|
live build for everyone.
|
|
134
146
|
|
|
147
|
+
⚠️ **It does not ask.** Choosing the action is the decision, so there is no
|
|
148
|
+
"upload N files?" confirmation to dismiss — the file count, size, destination and
|
|
149
|
+
any warnings go to the Console instead, where they survive being read. A build
|
|
150
|
+
that *cannot* upload still stops with a dialog naming the fix.
|
|
151
|
+
|
|
135
152
|
The upload is refused before a byte moves if the build is precompressed or has
|
|
136
153
|
no `index.html` at its top level, and OS metadata is dropped rather than sent —
|
|
137
154
|
which is also what makes a build pushed from Unity hash identically to the same
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "com.robotsquid.squidhub",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.9",
|
|
4
4
|
"displayName": "SquidHub",
|
|
5
5
|
"description": "Cloud saves, analytics and the coin economy for Unity WebGL games published on SquidHub. Installs the C# wrapper, the .jslib half and the page bridge, and rewrites the exported index.html so it survives the platform's CSP.",
|
|
6
6
|
"unity": "2021.3",
|