Last active
March 21, 2021 18:18
-
-
Save ImTheSquid/743a72e8e923ba79928509a13d3e9c1b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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