diff --git a/2020/otterness/.entry.json b/2020/otterness/.entry.json index 71b6d2aef..1aaab4556 100644 --- a/2020/otterness/.entry.json +++ b/2020/otterness/.entry.json @@ -33,6 +33,14 @@ "display_via_github" : true, "entry_text" : "original source code" }, + { + "file_path" : "try.alt.sh", + "inventory_order" : 100, + "OK_to_edit" : true, + "display_as" : "shellscript", + "display_via_github" : true, + "entry_text" : "script to try alternate code" + }, { "file_path" : "try.sh", "inventory_order" : 100, @@ -41,6 +49,14 @@ "display_via_github" : true, "entry_text" : "script to try entry" }, + { + "file_path" : "prog.alt.c", + "inventory_order" : 120, + "OK_to_edit" : true, + "display_as" : "c", + "display_via_github" : true, + "entry_text" : "unobfuscated source code" + }, { "file_path" : "cvikl.mid", "inventory_order" : 150, diff --git a/2020/otterness/.gitignore b/2020/otterness/.gitignore index b89f0ad1b..454f4b5d8 100644 --- a/2020/otterness/.gitignore +++ b/2020/otterness/.gitignore @@ -6,6 +6,7 @@ indent indent.c indent.o prog +prog.alt prog.orig !cvikl.mid !entertainer.mid diff --git a/2020/otterness/Makefile b/2020/otterness/Makefile index 1a89cefc6..9cc95acc2 100644 --- a/2020/otterness/Makefile +++ b/2020/otterness/Makefile @@ -115,8 +115,8 @@ OBJ= ${PROG}.o DATA= TARGET= ${PROG} # -ALT_OBJ= -ALT_TARGET= +ALT_OBJ= ${PROG}.alt.o +ALT_TARGET= ${PROG}.alt ################# @@ -139,6 +139,9 @@ ${PROG}: ${PROG}.c alt: data ${ALT_TARGET} @${TRUE} +${PROG}.alt: ${PROG}.alt.c + ${CC} ${CFLAGS} $< -o $@ ${LDFLAGS} + # data files # data: ${DATA} diff --git a/2020/otterness/README.md b/2020/otterness/README.md index 2ab1f5144..986a5ca00 100644 --- a/2020/otterness/README.md +++ b/2020/otterness/README.md @@ -4,6 +4,9 @@ make ``` +There is an unobfuscated version for those who wish to see the original final +prior to obfuscation. See [Alternate code](#alternate-code) below. + ### Bugs and (Mis)features: @@ -33,6 +36,37 @@ Now play `output.mid` and `output2.mid` in an audio player that can play MIDI files. +## Alternate code: + +### NOTICE to those who wish for a greater challenge: + +**If you want a greater challenge, don't check this version**: +just try to understand the program via the entry source. + +If you get stuck, come back and take a look at the alternate, unobfuscated code, +described by the author as: + +> If you're curious about the un-obfuscated program that I originally wrote, +this was the final working version. I tried to keep the functions names and +comments understandable, but it will make a lot more sense to readers familiar +with the MIDI "SMF" file format. + +... at their [web page about the +entry](https://www.nathanotterness.com/2021/04/obfuscated_c_2020.html). + + +## Alternate use: + +Use `prog.alt` as you would `prog`. + + +### Alternate try: + +``` + ./try.alt.sh +``` + + ## Judges' remarks: In an entry that boasts not using literals, one would expect to see @@ -105,6 +139,13 @@ especially true for "live performance" .mid files. correctly parse, but I have rarely seen this in practice. The largest cause of program failures, in my experience, has been Limitation 2. +### NOTICE to those who wish for a greater challenge: + +**If you want a greater challenge, don't read any further**: +just try to understand the program via the source. + +If you get stuck, come back and read below for additional hints and information. + #### Obfuscation notes diff --git a/2020/otterness/index.html b/2020/otterness/index.html index ecdf516f2..bce40cad3 100644 --- a/2020/otterness/index.html +++ b/2020/otterness/index.html @@ -409,6 +409,8 @@ Location: US - United States of America

To build:

    make
+

There is an unobfuscated version for those who wish to see the original final +prior to obfuscation. See Alternate code below.

Bugs and (Mis)features:

The current status of this entry is:

    STATUS: INABIAF - please DO NOT fix
@@ -419,6 +421,24 @@ Location: US - United States of America
    ./try.sh

Now play output.mid and output2.mid in an audio player that can play MIDI files.

+

Alternate code:

+

NOTICE to those who wish for a greater challenge:

+

If you want a greater challenge, don’t check this version: +just try to understand the program via the entry source.

+

If you get stuck, come back and take a look at the alternate, unobfuscated code, +described by the author as:

+
+

If you’re curious about the un-obfuscated program that I originally wrote, +this was the final working version. I tried to keep the functions names and +comments understandable, but it will make a lot more sense to readers familiar +with the MIDI “SMF” file format.

+
+

… at their web page about the +entry.

+

Alternate use:

+

Use prog.alt as you would prog.

+

Alternate try:

+
     ./try.alt.sh

Judges’ remarks:

In an entry that boasts not using literals, one would expect to see straightforward ways to produce small integers like !(x^x)<<!(x^x), etc. but @@ -537,7 +557,9 @@ very thoroughly).

  • prog.c - entry source code
  • Makefile - entry Makefile
  • prog.orig.c - original source code
  • +
  • try.alt.sh - script to try alternate code
  • try.sh - script to try entry
  • +
  • prog.alt.c - unobfuscated source code
  • cvikl.mid - sample MIDI file
  • entertainer.mid - sample MIDI file
  • diff --git a/2020/otterness/prog.alt.c b/2020/otterness/prog.alt.c new file mode 100644 index 000000000..f1a520cb4 --- /dev/null +++ b/2020/otterness/prog.alt.c @@ -0,0 +1,284 @@ +#include +#include +#include +#include + +uint16_t ReadBigEndian16(uint8_t *c) { + return *(c + 1) | (uint16_t) (*c) << 8; +} + +void WriteBigEndian16(uint8_t *c, uint16_t v) { + c[1] = v; + c[0] = v >> 8; +} + +uint32_t ReadBigEndian32(uint8_t *c) { + return (uint32_t) ReadBigEndian16(c) << 16 | ReadBigEndian16(c + 2); +} + +void WriteBigEndian32(uint8_t *c, uint32_t v) { + WriteBigEndian16(c + 2, v); + WriteBigEndian16(c, v >> 16); +} + +// Fills in the *value. Returns the length in bytes. +int ReadVarInt(uint8_t *content, uint32_t *value) { + int i; + uint8_t b; + *value = 0; + for (i = 0; i < 4; i++) { + b = content[i]; + *value |= b & 0x7f; + if (!(b & 0x80)) return i + 1; + *value = *value << 7; + } + fprintf(stderr, "Bad variable int.\n"); + exit(1); +} + +uint8_t HighestBit(uint32_t n) { + uint8_t r = 1; + if (!n) return 0; + while (n >>= 1) r++; + return r; +} + +uint8_t VarIntBytes(uint32_t n) { + uint8_t bits = HighestBit(n); + return bits / 7 + !!(bits % 7); +} + +int WriteVarInt(uint32_t n, uint8_t *c) { + uint8_t bytes_needed = VarIntBytes(n); + *c = 0; + if (!n) return bytes_needed; + if (bytes_needed > 4) { + fprintf(stderr, "Int too big for MIDI.\n"); + exit(1); + } + c += bytes_needed - 1; + while (n != 0) { + *c = n & 0x7f; + n = n >> 7; + c--; + } + return bytes_needed; +} + +// Advances past the next MIDI message, returning a pointer after it, and +// updating running status if necessary. +uint8_t* AdvancePastMessage(uint8_t *c, uint8_t *running_status) { + uint32_t tmp; + uint8_t b; + switch (*c) { + case 0xf0: + case 0xf7: + // Skip past system exclusive messages + *running_status = 0; + c++; + c += ReadVarInt(c, &tmp); + return c + tmp; + case 0xff: + // Skip past meta events + *running_status = 0; + c++; + c++; + c += ReadVarInt(c, &tmp); + return c + tmp; + } + // We're assuming this is a channel voice message now. + if (*c & 0x80) { + // Advance past the new status byte. + b = *c; + c++; + *running_status = b; + } else { + b = *running_status; + } + // Just test the different channel voice message types. + switch (b >> 4) { + case 8: + case 9: + case 10: + case 11: + case 14: + return c + 2; + case 12: + case 13: + return c + 1; + } + fprintf(stderr, "Bad message status byte: %x\n", b); + exit(1); + return c; +} + +// Assumes we're pointing at the start of a MTrk. Returns a pointer to the +// start of the next track. +uint8_t* GetTrackTicks(uint8_t *c, uint32_t *ticks) { + uint32_t length, tmp; + uint8_t *end; + uint8_t running_status = 0; + *ticks = 0; + if (c[3] != 'k') { + fprintf(stderr, "Not at a track start.\n"); + exit(1); + } + length = ReadBigEndian32(c + 4); + c += 8; + end = c + length; + while (c < end) { + c += ReadVarInt(c, &tmp); + *ticks += tmp; + c = AdvancePastMessage(c, &running_status); + } + return c; +} + +// Requires a pointer to the start of the first track, and the total number of +// tracks. +uint32_t GetHighestTrackTicks(uint8_t *c, int track_count) { + uint32_t tmp, max = 0; + int i; + for (i = 0; i < track_count; i++) { + c = GetTrackTicks(c, &tmp); + fprintf(stderr, "Track %d: %u ticks\n", i, (unsigned) tmp); + if (tmp > max) max = tmp; + } + return max; +} + +void DumpHex(uint8_t *data, int size) { + int i; + for (i = 0; i < size; i++) { + fprintf(stderr, "%p + %d: 0x%02x\n", data, i, data[i]); + } +} + +uint8_t *beat = NULL; +uint8_t *note_off_note = NULL; +// Writes a beat at the given offset. Requires the beat size. +uint8_t* WriteBeat(uint8_t *c, int i, int bs) { + uint8_t n = 42, v = 80; + if ((i % 4) == 0) { + n = 36; + v = 120; + } else if ((i % 4) == 2) { + n = 40; + v = 100; + } + beat[1] = n; + beat[2] = v; + *note_off_note = n; + memcpy(c, beat, bs); + return c + bs; +} + +void WriteBootsAndCats(uint8_t *c, uint32_t length) { + uint16_t ticks_per_beat, track_count; + uint32_t beats_to_generate, tmp, data_size, beat_size; + uint8_t *cur, *data = NULL; + track_count = ReadBigEndian16(c + 10); + ticks_per_beat = ReadBigEndian16(c + 12) >> 1; + if (ticks_per_beat >> 15) { + fprintf(stderr, "SMPTE timing isn't supported\n"); + exit(1); + } + beats_to_generate = GetHighestTrackTicks(c + 14, track_count) / ticks_per_beat; + // A "beat" is simply a note-on followed by a note-off event. The tricky part + // is the delta time may be more than one byte. We'll assume running status. + // Note on = 00 + // Note off = 00 + beat_size = 5 + VarIntBytes(ticks_per_beat); + beat = malloc(beat_size); + if (!beat) { + fprintf(stderr, "Failed allocating beat.\n"); + exit(1); + } + // This requires 8 bytes in the track header + 4 bytes for end-of-track + + // the track data, plus an additional byte to set up running status in the + // first beat. + data_size = beats_to_generate * beat_size + 13; + data = malloc(data_size); + if (!data) { + fprintf(stderr, "Failed allocating data.\n"); + exit(1); + } + fprintf(stderr, "Data size: %d (0x%x), beat size %d, # beats %d\n", + (int) data_size, data_size, beat_size, beats_to_generate); + memcpy(data, "MTrk", 4); + WriteBigEndian32(data + 4, data_size - 8); + fprintf(stderr, "Data size: %d\n", (int) data_size); + + // Build the "beat" data + cur = beat; + *cur++ = 0; + // Initial note number and velocity. + *cur++ = 36; + *cur++ = 120; + cur += WriteVarInt(ticks_per_beat, cur); + // Keep a pointer to where to write the note to turn off + note_off_note = cur; + *cur++ = 36; + // The note-off velocity is 0. + *cur = 0; + + cur = data + 8; + // Write the first note-on and off: + *cur++ = 0; + *cur++ = 0x99; + memcpy(cur, beat + 1, beat_size - 1); + cur += beat_size - 1; + for (tmp = 1; tmp < beats_to_generate; tmp++) { + cur = WriteBeat(cur, tmp, beat_size); + } + *cur++ = 0; + *cur++ = 0xff; + *cur++ = 0x2f; + *cur = 0; + // Update the original MIDI header to increment the track count and make sure + // the format is 1. + c[9] = 1; + WriteBigEndian16(c + 10, track_count + 1); + fwrite(c, length, 1, stdout); + fwrite(data, data_size, 1, stdout); +} + +int main(int argc, char **argv) { + FILE *f = NULL; + uint8_t *content = NULL; + long size = 0; + int result; + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + f = fopen(argv[1], "rb"); + if (!f) { + fprintf(stderr, "Coudn't open %s\n", argv[1]); + return 1; + } + fseek(f, 0, SEEK_END); + size = ftell(f); + fseek(f, 0, SEEK_SET); + content = malloc(size); + if (!content) { + fprintf(stderr, "Couldn't allocate content buffer.\n"); + fclose(f); + return 1; + } + result = fread(content, size, 1, f); + fclose(f); + if (result != 1) { + fprintf(stderr, "Couldn't read %s.\n", argv[1]); + free(content); + return 1; + } + if ((content[3] != 'd') || (content[7] != 6)) { + fprintf(stderr, "Bad MIDI file.\n"); + free(content); + return 1; + } + WriteBootsAndCats(content, size); + free(content); + return 0; +} diff --git a/2020/otterness/try.alt.sh b/2020/otterness/try.alt.sh new file mode 100755 index 000000000..5adc123c4 --- /dev/null +++ b/2020/otterness/try.alt.sh @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# try.sh - demonstrate IOCCC entry 2020/otterness alt code +# + +# make sure CC is set so that when we do make CC="$CC" it isn't empty. Doing it +# this way allows us to have the user specify a different compiler in an easy +# way. +[[ -z "$CC" || ! -f "$CC" || ! -x "$CC" ]] && CC="cc" + +make CC="$CC" alt >/dev/null || exit 1 + +# clear screen after compilation so that only the entry is shown +clear + +read -r -n 1 -p "Press any key to run: ./prog.alt < cvikl.mid > output.mid: " +echo 1>&2 +./prog.alt < cvikl.mid > output.mid +echo "Now play output.wav in an audio player that can play MIDI files." 1>&2 +echo 1>&2 + +read -r -n 1 -p "Press any key to run: ./prog.alt < entertainer.mid > output2.mid: " +echo 1>&2 +./prog.alt < entertainer.mid > output2.mid +echo "Now play output2.wav in an audio player that can play MIDI files." 1>&2 +echo 1>&2 diff --git a/thanks-for-help.html b/thanks-for-help.html index e219a1aee..a048be0d9 100644 --- a/thanks-for-help.html +++ b/thanks-for-help.html @@ -4659,7 +4659,10 @@ Tolkien.

    entertainer.mid from the URLs we suggested so that one need not download them and to make sure they can always be obtained even if the domain or link goes dead.

    -

    Cody also added the try.sh script.

    +

    Cody also added the try.sh script and the +try.alt.sh script for the author’s +unobfuscated version that Cody added as +prog.alt.c.

    Winning entry: 2020/tsoj

    Winning entry source code: prog.c

    diff --git a/thanks-for-help.md b/thanks-for-help.md index 37691468b..549e44779 100644 --- a/thanks-for-help.md +++ b/thanks-for-help.md @@ -6038,7 +6038,10 @@ Tolkien](https://www.tolkienestate.com/life/biography/). that one need not download them and to make sure they can always be obtained even if the domain or link goes dead. -Cody also added the [try.sh](%%REPO_URL%%/2020/otterness/try.sh) script. +Cody also added the [try.sh](%%REPO_URL%%/2020/otterness/try.sh) script and the +[try.alt.sh](%%REPO_URL%%/2020/otterness/try.alt.sh) script for the author's +unobfuscated version that Cody added as +[prog.alt.c](%%REPO_URL%%/2020/otterness/prog.alt.c).
    diff --git a/tmp/manifest.csv b/tmp/manifest.csv index b2f75ff4f..301307f23 100644 --- a/tmp/manifest.csv +++ b/tmp/manifest.csv @@ -4319,7 +4319,9 @@ 2020,otterness,prog.c,20,true,c,true,entry source code 2020,otterness,Makefile,30,true,makefile,true,entry Makefile 2020,otterness,prog.orig.c,50,false,c,true,original source code +2020,otterness,try.alt.sh,100,true,shellscript,true,script to try alternate code 2020,otterness,try.sh,100,true,shellscript,true,script to try entry +2020,otterness,prog.alt.c,120,true,c,true,unobfuscated source code 2020,otterness,cvikl.mid,150,true,midi,false,sample MIDI file 2020,otterness,entertainer.mid,159,true,midi,false,sample MIDI file 2020,otterness,2020_otterness.tar.bz2,1415926535,false,tbz2,false,download entry tarball diff --git a/tmp/manifest.numbers b/tmp/manifest.numbers index 374da2847..5889f6f1a 100644 Binary files a/tmp/manifest.numbers and b/tmp/manifest.numbers differ diff --git a/tmp/path_list.found.txt b/tmp/path_list.found.txt index ac46688af..908d93634 100644 --- a/tmp/path_list.found.txt +++ b/tmp/path_list.found.txt @@ -4324,8 +4324,10 @@ 2020/otterness/cvikl.mid 2020/otterness/entertainer.mid 2020/otterness/index.html +2020/otterness/prog.alt.c 2020/otterness/prog.c 2020/otterness/prog.orig.c +2020/otterness/try.alt.sh 2020/otterness/try.sh 2020/tsoj/.entry.json 2020/tsoj/.gitignore diff --git a/tmp/path_list.manifest.txt b/tmp/path_list.manifest.txt index ac46688af..908d93634 100644 --- a/tmp/path_list.manifest.txt +++ b/tmp/path_list.manifest.txt @@ -4324,8 +4324,10 @@ 2020/otterness/cvikl.mid 2020/otterness/entertainer.mid 2020/otterness/index.html +2020/otterness/prog.alt.c 2020/otterness/prog.c 2020/otterness/prog.orig.c +2020/otterness/try.alt.sh 2020/otterness/try.sh 2020/tsoj/.entry.json 2020/tsoj/.gitignore diff --git a/tmp/path_list.required.txt b/tmp/path_list.required.txt index 9a9c51f54..1526a78d6 100644 --- a/tmp/path_list.required.txt +++ b/tmp/path_list.required.txt @@ -3091,7 +3091,9 @@ 2020/otterness/README.md 2020/otterness/cvikl.mid 2020/otterness/entertainer.mid +2020/otterness/prog.alt.c 2020/otterness/prog.c +2020/otterness/try.alt.sh 2020/otterness/try.sh 2020/tsoj/.entry.json 2020/tsoj/.gitignore