Skip to content

Instantly share code, notes, and snippets.

@ImTheSquid
Last active March 21, 2021 18:18
Show Gist options
  • Save ImTheSquid/743a72e8e923ba79928509a13d3e9c1b to your computer and use it in GitHub Desktop.
Save ImTheSquid/743a72e8e923ba79928509a13d3e9c1b to your computer and use it in GitHub Desktop.
// MIT License
// Copyright (c) 2021 Jack Hogan
// 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.
// GIF Loop Configuration Utility 2
// Requires standard C compiler
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
// Util functions
void removeElements(unsigned char **array, size_t index, size_t charsToDelete, size_t arrayLength) {
for(int i = index; i < arrayLength - charsToDelete; i++){
(*array)[i] = (*array)[i + charsToDelete];
}
unsigned char *newArr = (unsigned char*)realloc(*array, (arrayLength - charsToDelete) * sizeof(unsigned char));
if (newArr == NULL) {
printf("Failed to reallocate memory!\n");
exit(5);
}
array = &newArr;
}
void escapeSubBlocks(unsigned char **fileContents) {
// Find size bit and skip until its zero
unsigned char nextSize;
do {
nextSize = **fileContents;
*fileContents += nextSize + 1;
}
while (nextSize);
}
void handleLogicalScreenDescriptor(unsigned char **fileContents) {
// Seek to packed field
*fileContents += 4;
unsigned char packed;
packed = **fileContents;
// Seek to next section
*fileContents += 3;
// Is there a global color table?
if (packed >> 7) {
// Read size bits and seek past table
size_t size = 3 * pow(2, (packed & 0x7) + 1);
*fileContents += size;
}
}
void handleHeader(unsigned char **fileContents) {
// Skip header field
*fileContents += 6;
handleLogicalScreenDescriptor(fileContents);
}
void insertApplicationExtension(unsigned char **fileContents, unsigned char *start, size_t length, char *path, uint16_t loopCount) {
*fileContents = start;
// Fast forward past header
handleHeader(fileContents);
// Check how long the header was and write only it to file
size_t headerSize = *fileContents - start;
*fileContents = start;
FILE *f = fopen(path, "wb");
for (int i = 0; i < headerSize; ++i) {
fputc((*fileContents)[i], f);
}
// Write a new extension
unsigned char extension[] = {0x21, 0xFF, 0x0B, 0x4E, 0x45, 0x54, 0x53, 0x43, 0x41, 0x50, 0x45, 0x32, 0x2E, 0x30, 0x03, 0x01, 0x00, 0x00, 0x00};
extension[16] = loopCount & 0xFF;
extension[17] = loopCount >> 8;
for (int i = 0; i < 19; ++i) {
fputc(extension[i], f);
}
// Write the rest of the file starting from where the new extension ends
for (int i = headerSize; i < length; ++i) {
fputc((*fileContents)[i], f);
}
}
// Keep track of application extension
unsigned char *netscapeExtension = NULL;
void removeApplicationExtension(unsigned char **fileContents, unsigned char *start, size_t length, char *path) {
*fileContents = start;
removeElements(fileContents, netscapeExtension - start, 0x13, length);
// Rewrite new file to disk
FILE *f = fopen(path, "wb");
for (size_t i = 0; i < length - 0x13; ++i) {
fputc((*fileContents)[i], f);
}
fclose(f);
}
// Skipping functions
void handleCommentExtension(unsigned char **fileContents) {
escapeSubBlocks(fileContents);
}
void handleApplicationExtension(unsigned char **fileContents) {
// Skip Application Block
unsigned char toSkip;
toSkip = **fileContents;
if (netscapeExtension == NULL && toSkip == 0x0B && strncmp((char*)(*fileContents + 1), "NETSCAPE2.0", 0x0B) == 0) {
netscapeExtension = *fileContents - 2;
}
*fileContents += toSkip + 6;
}
void handlePlainTextExtension(unsigned char **fileContents) {
// Skip required bytes
unsigned char toSkip;
toSkip = **fileContents;
*fileContents += toSkip + 1;
}
void handleImageData(unsigned char **fileContents) {
// Seek past LZW
(*fileContents)++;
escapeSubBlocks(fileContents);
}
void handleImageDescriptor(unsigned char **fileContents) {
*fileContents += 9;
// Is there a local color table?
if (*(*fileContents - 1) >> 7 == 0x01) {
// Read size bits and seek past table
int increment = 3 * pow(2, (*(*fileContents - 1) & 0x7) + 1);
*fileContents += increment;
}
handleImageData(fileContents);
}
void handleGraphicsControlExtension(unsigned char **fileContents) {
// Seek out of extension
*fileContents += 6;
}
void parseFile(unsigned char **fileContents) {
unsigned char *start = *fileContents;
handleHeader(fileContents);
// While not at EOF
while (**fileContents != 0x3B) {
unsigned char header = *(*fileContents)++;
if (header == 0x2C) {
handleImageDescriptor(fileContents);
}
else if (header == 0x21) {
unsigned char next = *(*fileContents)++;
switch (next) {
case 0x01:
handlePlainTextExtension(fileContents);
break;
case 0xF9:
handleGraphicsControlExtension(fileContents);
break;
case 0xFE:
handleCommentExtension(fileContents);
break;
case 0xFF:
handleApplicationExtension(fileContents);
break;
default:
printf("Unknown extension header 0x%X at offset 0x%lx\n", *(*fileContents - 1), *fileContents - 1 - start);
exit(4);
}
}
else {
printf("Unknown header 0x%X at offset 0x%lx\n", **fileContents, *fileContents - start);
exit(4);
}
}
}
void toggleGifLoop(unsigned char *fileContents, size_t fileSize, char *path) {
unsigned char *start = fileContents;
parseFile(&fileContents);
if (netscapeExtension == NULL) {
// If it doesn't exist, create it with an infinite loop
printf("Setting to play infinitely\n");
insertApplicationExtension(&fileContents, start, fileSize, path, 0);
}
else {
// If it does, then read and toggle it
fileContents = netscapeExtension + 2;
// Skip over most of it
handleApplicationExtension(&fileContents);
// Read bytes
fileContents -= 3;
uint16_t loopCount = (*(fileContents + 1) << 8) + *fileContents;
if (loopCount == 0) {
printf("Setting to play once\n");
removeApplicationExtension(&fileContents, start, fileSize, path);
}
else {
printf("Setting to play infinitely\n");
*fileContents = 0;
*(++fileContents) = 0;
fileContents = start;
FILE *f = fopen(path, "wb");
for (int i = 0; i < fileSize; ++i) {
fputc(fileContents[i], f);
}
fclose(f);
}
}
}
void setGifLoop(unsigned char *fileContents, uint16_t loopCount, size_t fileSize, char *path) {
unsigned char *start = fileContents;
parseFile(&fileContents);
if (netscapeExtension == NULL && loopCount > 0) {
// If it doesn't exist, create it
insertApplicationExtension(&fileContents, start, fileSize, path, loopCount);
}
else {
// If it does, then read and update it or delete it
if (loopCount == 0) {
removeApplicationExtension(&fileContents, start, fileSize, path);
}
else {
// Seek to netscape extension
fileContents = netscapeExtension + 2;
// Skip over most of it
handleApplicationExtension(&fileContents);
// Write bytes
fileContents -= 3;
*fileContents = (loopCount < 0xFFFF ? loopCount : 0) & 0xFF;
*(++fileContents) = (loopCount < 0xFFFF ? loopCount : 0) >> 8;
fileContents = start;
FILE *f = fopen(path, "wb");
for (int i = 0; i < fileSize; ++i) {
fputc(fileContents[i], f);
}
fclose(f);
}
}
}
bool verifyIsGif(FILE *f) {
unsigned char version[7];
fread(version, 1, 6, f);
version[6] = '\0';
return strcmp((char*)version, "GIF89a") == 0;
}
// Opens file and returns its size
size_t openFile(char *path, FILE **f) {
(*f) = fopen(path, "rb+");
printf("Reading file...\n");
if (*f == NULL) {
perror("Error reading file");
exit(2);
}
printf("Verifying type...\n");
if (!verifyIsGif(*f)) {
printf("File is not a GIF or is not of standard GIF89a\n");
fclose(*f);
exit(3);
}
printf("File is a GIF\n");
// Get file size
fseek(*f, 0, SEEK_END);
size_t size = ftell(*f);
rewind(*f);
return size;
}
int main(int argc, char** argv) {
printf("=== GIF Loop Configuration Utility 2 ===\n");
printf("Copyright 2021 Jack Hogan\n");
printf("MIT License\n\n");
FILE *f = NULL;
unsigned char *fileContents = NULL;
if (argc == 1) {
// No arguments supplied
printf("Usage: gifLoop <file> [playTimes]\n\n");
printf("playTimes: Optional integer from 0-65535 (0=infinite plays), leaving blank will toggle between infinite and single play mode\n\n");
printf("Example Usage: gifLoop myGif.gif 20\n");
printf("Example Usage: gifLoop myGif.gif\n");
return 0;
}
else if (argc > 3) {
printf("Too many arguments given");
return 1;
}
size_t size = openFile(argv[1], &f);
printf("File size is %lu bytes\n", size);
fileContents = (unsigned char*)malloc(size * sizeof(unsigned char));
fread(fileContents, 1, size, f);
fclose(f);
if (argc == 2) {
// Toggle loop
printf("Toggling GIF loop\n");
toggleGifLoop(fileContents, size, argv[1]);
}
else {
// Loop information given
uint16_t count = (uint16_t)atoi(argv[2]);
printf("Setting GIF play count to %d\n", count);
setGifLoop(fileContents, count - 1, size, argv[1]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment