diff options
| author | Simon Tatham <anakin@pobox.com> | 2021-05-23 08:45:55 +0100 |
|---|---|---|
| committer | Simon Tatham <anakin@pobox.com> | 2021-05-23 08:45:55 +0100 |
| commit | f729f51e475ff98d0caf529f0723ef810b1c88ef (patch) | |
| tree | 5f76f07e6dfe9b3f38f5a4966d9ce92c398771dd | |
| parent | 1c760b2ee808ba68781a68a57292cc841b3df5a0 (diff) | |
| download | puzzles-f729f51e475ff98d0caf529f0723ef810b1c88ef.zip puzzles-f729f51e475ff98d0caf529f0723ef810b1c88ef.tar.gz puzzles-f729f51e475ff98d0caf529f0723ef810b1c88ef.tar.bz2 puzzles-f729f51e475ff98d0caf529f0723ef810b1c88ef.tar.xz | |
WASM: move save file encoding from JS into C.
The previous fix worked OK, but it was conceptually wrong. Puzzles
save files are better regarded as binary, not text: the length fields
are measured in bytes, so translating the file into a different
multibyte character encoding would invalidate them.
So it was wrong to fetch a C byte string containing the exactly right
binary data, then translate it into a Javascript string as if decoding
from UTF-8, then retranslate to a representation of a bytewise
encoding via encodeURIComponent, and then label the result as
application/octet-stream.
This probably wouldn't have caused any problems in practice, because I
don't remember any situation in which my save files use characters
outside printable ASCII (plus newline). But it's not actually
forbidden, so a save file might choose to do that some day, so that
UTF-8 decode/reencode hidden in the JS was a latent bug.
Now the URI-encoding is done on the C side, while we still know
exactly what the binary data ought to look like and can be sure we're
translating it byte for byte into the output encoding for the data:
URI. By the time the JS receives a string pointer from get_save_file,
it's already URI-encoded, which _guarantees_ that it's in ASCII and
won't be messed about with by Emscripten's UTF8ToString.
| -rw-r--r-- | emcc.c | 49 | ||||
| -rw-r--r-- | emccpre.js | 3 |
2 files changed, 46 insertions, 6 deletions
@@ -787,12 +787,53 @@ struct savefile_write_ctx { size_t pos; }; -static void savefile_write(void *vctx, const void *buf, int len) +static void savefile_write(void *vctx, const void *vbuf, int len) { + static const unsigned char length[256] = { + /* + * Assign a length of 1 to any printable ASCII character that + * can be written literally in URI-encoding, i.e. + * + * A-Z a-z 0-9 - _ . ! ~ * ' ( ) + * + * Assign length 3 (for % and two hex digits) to all other + * byte values. + */ + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 1, 3, 3, 3, 3, 3, 1, 1, 1, 1, 3, 3, 1, 1, 3, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 3, 3, + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 3, 1, + 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 3, 3, 1, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + }; + static const char hexdigits[] = "0123456789ABCDEF"; + struct savefile_write_ctx *ctx = (struct savefile_write_ctx *)vctx; - if (ctx->buffer) - memcpy(ctx->buffer + ctx->pos, buf, len); - ctx->pos += len; + const unsigned char *buf = (const unsigned char *)vbuf; + for (int i = 0; i < len; i++) { + unsigned char c = buf[i]; + int clen = length[c]; + if (ctx->buffer) { + if (clen == 1) { + ctx->buffer[ctx->pos] = c; + } else { + ctx->buffer[ctx->pos] = '%'; + ctx->buffer[ctx->pos+1] = hexdigits[c >> 4]; + ctx->buffer[ctx->pos+2] = hexdigits[c & 0xF]; + } + } + ctx->pos += clen; + } } char *get_save_file(void) @@ -379,8 +379,7 @@ function initPuzzle() { "Click to download the ")); var a = document.createElement("a"); a.download = "puzzle.sav"; - a.href = "data:application/octet-stream," + - encodeURIComponent(savefile_text); + a.href = "data:application/octet-stream," + savefile_text; a.appendChild(document.createTextNode("saved-game file")); dlg_form.appendChild(a); dlg_form.appendChild(document.createTextNode(".")); |