Task 253: .GDSCRIPT File Format

Task 253: .GDSCRIPT File Format

1. List of All Properties of the .GDScript File Format Intrinsic to Its File System

.GDSCRIPT files (commonly using the .gd extension) are plain text files containing GDScript source code for the Godot Engine. They have no binary structure or embedded metadata beyond standard text file conventions. The following is a comprehensive list of properties intrinsic to the format, based on official Godot documentation and conventions for storage and handling in file systems:

  • File Extension: .gd (used to identify GDScript files in Godot projects and file systems).
  • Format Type: Plain text (human-readable, no binary encoding or headers specific to the format).
  • Default Encoding: UTF-8 without BOM (Byte Order Mark), supporting full Unicode characters in identifiers, strings, and comments.
  • Line Endings: LF (\n) – Unix-style line feeds are the standard; CRLF (\r\n) may be used but is not preferred.
  • Indentation Style: Tab characters (\t) for code blocks and nesting (indentation-based syntax similar to Python).
  • File Termination: Files should end with exactly one newline (LF) character.
  • Character Set Support: Full Unicode (via UTF-8), including escape sequences like \uXXXX (UTF-16) and \UXXXXXX (UTF-32) for codepoints.
  • MIME Type: text/plain (commonly treated as text/x-gdscript in editors like VS Code for syntax highlighting).
  • BOM Handling: No BOM expected; presence indicates non-standard encoding.
  • File System Integration: Stored as regular files in Godot's res:// virtual file system; loaded via preload() or load() functions, with no special permissions or attributes beyond OS defaults.

These properties ensure consistency, readability, and compatibility across platforms in Godot projects.

Here are two direct links to sample .gd (GDScript) files from official Godot demo projects on GitHub. These are raw text files you can download directly:

3. Ghost Blog Embedded HTML JavaScript for Drag-and-Drop .GDSCRIPT File Analysis

Below is an embeddable HTML snippet for a Ghost blog post. It includes a drag-and-drop zone for .gd files. Upon dropping a file, it reads the file as UTF-8 text, detects and dumps the format properties (from the list above) to the screen, including file-specific detections like actual line endings, indentation type, and BOM presence. It verifies conformance to the format where possible. Paste this into a Ghost post's HTML view.

Drag and drop a .gd (GDScript) file here to analyze its format properties.

This script uses the browser's File API for drag-and-drop and reads the file asynchronously. It detects properties dynamically and highlights non-standard ones.

4. Python Class for .GDSCRIPT File Handling

This Python class opens a .gd file, decodes it as UTF-8, reads the content, prints the format properties (verifying/detecting where possible), and writes it back with standard formatting (UTF-8, LF endings, no BOM).

import os
import re

class GDScriptHandler:
    def __init__(self, filepath):
        self.filepath = filepath
        self.content = None
        self.properties = {
            'extension': '.gd',
            'format_type': 'Plain text',
            'encoding': 'UTF-8',
            'line_endings': 'LF (\\n)',
            'indentation': 'Tabs (\\t)',
            'file_termination': 'Single newline',
            'character_set': 'Unicode (UTF-8)',
            'mime_type': 'text/plain'
        }

    def read_and_decode(self):
        """Read and decode the file as UTF-8."""
        try:
            with open(self.filepath, 'r', encoding='utf-8', newline='') as f:
                self.content = f.read()
            self._detect_properties()
            self._print_properties()
            return self.content
        except Exception as e:
            print(f"Error reading file: {e}")
            return None

    def _detect_properties(self):
        """Detect file-specific properties."""
        if self.content.startswith('\ufeff'):
            self.properties['bom_presence'] = 'Yes (Non-standard)'
        else:
            self.properties['bom_presence'] = 'No - Standard'
        
        if '\r\n' in self.content:
            self.properties['detected_line_endings'] = 'CRLF (\\r\\n) - Non-standard'
        else:
            self.properties['detected_line_endings'] = 'LF (\\n) - Standard'
        
        # Detect indentation
        indent_match = re.search(r'^\s+', self.content, re.MULTILINE)
        if indent_match:
            if '\t' in indent_match.group():
                self.properties['detected_indentation'] = 'Tabs (\\t) - Standard'
            else:
                self.properties['detected_indentation'] = 'Spaces - Non-standard'
        else:
            self.properties['detected_indentation'] = 'None detected'
        
        if not self.content.endswith('\n'):
            self.properties['file_termination_detected'] = 'Non-standard'
        else:
            self.properties['file_termination_detected'] = 'Single newline - Standard'

    def _print_properties(self):
        """Print all properties to console."""
        print("GDScript File Properties:")
        for key, value in self.properties.items():
            print(f"{key.replace('_', ' ').title()}: {value}")
        print(f"File Size: {os.path.getsize(self.filepath)} bytes")
        print("\nRaw Content Preview:")
        print(self.content[:500] + "..." if len(self.content) > 500 else self.content)

    def write(self, output_path=None):
        """Write the content back with standard format (UTF-8, LF, no BOM)."""
        if output_path is None:
            output_path = self.filepath
        try:
            with open(output_path, 'w', encoding='utf-8', newline='\n') as f:
                # Remove BOM if present
                clean_content = self.content.lstrip('\ufeff')
                # Ensure single LF termination
                if not clean_content.endswith('\n'):
                    clean_content += '\n'
                f.write(clean_content)
            print(f"File written successfully to {output_path}")
        except Exception as e:
            print(f"Error writing file: {e}")

# Example usage:
# handler = GDScriptHandler('example.gd')
# handler.read_and_decode()
# handler.write()

5. Java Class for .GDSCRIPT File Handling

This Java class uses java.nio.file to open a .gd file, decode as UTF-8, read the content, print the properties (with detections), and write back with standard LF endings.

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class GDScriptHandler {
    private final Path filepath;
    private String content;
    private final java.util.Map<String, String> properties;

    public GDScriptHandler(String filepath) {
        this.filepath = Paths.get(filepath);
        this.properties = new java.util.HashMap<>();
        properties.put("extension", ".gd");
        properties.put("format_type", "Plain text");
        properties.put("encoding", "UTF-8");
        properties.put("line_endings", "LF (\\n)");
        properties.put("indentation", "Tabs (\\t)");
        properties.put("file_termination", "Single newline");
        properties.put("character_set", "Unicode (UTF-8)");
        properties.put("mime_type", "text/plain");
    }

    public String readAndDecode() {
        try {
            content = Files.readString(filepath, StandardCharsets.UTF_8);
            detectProperties();
            printProperties();
            return content;
        } catch (IOException e) {
            System.err.println("Error reading file: " + e.getMessage());
            return null;
        }
    }

    private void detectProperties() {
        if (content.startsWith("\ufeff")) {
            properties.put("bom_presence", "Yes (Non-standard)");
        } else {
            properties.put("bom_presence", "No - Standard");
        }

        if (content.contains("\r\n")) {
            properties.put("detected_line_endings", "CRLF (\\r\\n) - Non-standard");
        } else {
            properties.put("detected_line_endings", "LF (\\n) - Standard");
        }

        Pattern indentPattern = Pattern.compile("^\\s+", Pattern.MULTILINE);
        Matcher matcher = indentPattern.matcher(content);
        if (matcher.find()) {
            String indent = matcher.group();
            if (indent.contains("\t")) {
                properties.put("detected_indentation", "Tabs (\\t) - Standard");
            } else {
                properties.put("detected_indentation", "Spaces - Non-standard");
            }
        } else {
            properties.put("detected_indentation", "None detected");
        }

        if (!content.endsWith("\n")) {
            properties.put("file_termination_detected", "Non-standard");
        } else {
            properties.put("file_termination_detected", "Single newline - Standard");
        }
    }

    private void printProperties() {
        System.out.println("GDScript File Properties:");
        for (java.util.Map.Entry<String, String> entry : properties.entrySet()) {
            System.out.println(entry.getKey().replace("_", " ").toUpperCase() + ": " + entry.getValue());
        }
        try {
            System.out.println("File Size: " + Files.size(filepath) + " bytes");
        } catch (IOException e) {
            System.err.println("Error getting size: " + e.getMessage());
        }
        System.out.println("\nRaw Content Preview:");
        System.out.println(content.length() > 500 ? content.substring(0, 500) + "..." : content);
    }

    public void write(String outputPath) {
        try {
            String cleanContent = content.replaceAll("^\ufeff", ""); // Remove BOM
            if (!cleanContent.endsWith("\n")) {
                cleanContent += "\n";
            }
            // Replace CRLF with LF for standard
            cleanContent = cleanContent.replace("\r\n", "\n");
            Files.write(Paths.get(outputPath), cleanContent.getBytes(StandardCharsets.UTF_8));
            System.out.println("File written successfully to " + outputPath);
        } catch (IOException e) {
            System.err.println("Error writing file: " + e.getMessage());
        }
    }

    // Example usage:
    // GDScriptHandler handler = new GDScriptHandler("example.gd");
    // handler.readAndDecode();
    // handler.write("output.gd");
}

Compile and run with: javac GDScriptHandler.java then java GDScriptHandler (adjust for main method if needed).

6. JavaScript Class for .GDSCRIPT File Handling

This Node.js-compatible JavaScript class (ES6) reads a .gd file using fs, decodes as UTF-8, prints properties (with detections), and writes back with standard formatting. Run in Node.js.

const fs = require('fs');
const path = require('path');

class GDScriptHandler {
  constructor(filepath) {
    this.filepath = filepath;
    this.content = null;
    this.properties = {
      extension: '.gd',
      format_type: 'Plain text',
      encoding: 'UTF-8',
      line_endings: 'LF (\\n)',
      indentation: 'Tabs (\\t)',
      file_termination: 'Single newline',
      character_set: 'Unicode (UTF-8)',
      mime_type: 'text/plain'
    };
  }

  readAndDecode() {
    try {
      this.content = fs.readFileSync(this.filepath, 'utf8');
      this.detectProperties();
      this.printProperties();
      return this.content;
    } catch (e) {
      console.error('Error reading file:', e.message);
      return null;
    }
  }

  detectProperties() {
    if (this.content.startsWith('\ufeff')) {
      this.properties.bom_presence = 'Yes (Non-standard)';
    } else {
      this.properties.bom_presence = 'No - Standard';
    }

    if (this.content.includes('\r\n')) {
      this.properties.detected_line_endings = 'CRLF (\\r\\n) - Non-standard';
    } else {
      this.properties.detected_line_endings = 'LF (\\n) - Standard';
    }

    const indentMatch = this.content.match(/^\s+/m);
    if (indentMatch) {
      if (indentMatch[0].includes('\t')) {
        this.properties.detected_indentation = 'Tabs (\\t) - Standard';
      } else {
        this.properties.detected_indentation = 'Spaces - Non-standard';
      }
    } else {
      this.properties.detected_indentation = 'None detected';
    }

    if (!this.content.endsWith('\n')) {
      this.properties.file_termination_detected = 'Non-standard';
    } else {
      this.properties.file_termination_detected = 'Single newline - Standard';
    }
  }

  printProperties() {
    console.log('GDScript File Properties:');
    for (const [key, value] of Object.entries(this.properties)) {
      console.log(`${key.replace('_', ' ').toUpperCase()}: ${value}`);
    }
    const stats = fs.statSync(this.filepath);
    console.log(`File Size: ${stats.size} bytes`);
    console.log('\nRaw Content Preview:');
    console.log(this.content.length > 500 ? this.content.substring(0, 500) + '...' : this.content);
  }

  write(outputPath = this.filepath) {
    try {
      let cleanContent = this.content.replace(/^\ufeff/, ''); // Remove BOM
      if (!cleanContent.endsWith('\n')) {
        cleanContent += '\n';
      }
      cleanContent = cleanContent.replace(/\r\n/g, '\n'); // Normalize to LF
      fs.writeFileSync(outputPath, cleanContent, 'utf8');
      console.log(`File written successfully to ${outputPath}`);
    } catch (e) {
      console.error('Error writing file:', e.message);
    }
  }
}

// Example usage:
// const handler = new GDScriptHandler('example.gd');
// handler.readAndDecode();
// handler.write();

Run with: node script.js (wrap in a main if needed).

7. C Class for .GDSCRIPT File Handling

This C implementation (using stdio and string.h) opens a .gd file in binary mode for accurate byte reading, decodes assuming UTF-8 (simple char handling), prints properties (basic detections), and writes back with LF normalization. Compile with: gcc gdscript_handler.c -o handler.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

typedef struct {
    char* filepath;
    char* content;
    size_t size;
    struct {
        char extension[4];
        char format_type[12];
        char encoding[7];
        char line_endings[8];
        char indentation[10];
        char file_termination[16];
        char character_set[16];
        char mime_type[11];
        char bom_presence[20];
        char detected_line_endings[25];
        char detected_indentation[25];
        char file_termination_detected[25];
    } properties;
} GDScriptHandler;

void init_properties(GDScriptHandler* handler) {
    strcpy(handler->properties.extension, ".gd");
    strcpy(handler->properties.format_type, "Plain text");
    strcpy(handler->properties.encoding, "UTF-8");
    strcpy(handler->properties.line_endings, "LF (\\n)");
    strcpy(handler->properties.indentation, "Tabs (\\t)");
    strcpy(handler->properties.file_termination, "Single newline");
    strcpy(handler->properties.character_set, "Unicode (UTF-8)");
    strcpy(handler->properties.mime_type, "text/plain");
}

int read_and_decode(GDScriptHandler* handler) {
    FILE* file = fopen(handler->filepath, "rb");
    if (!file) {
        perror("Error opening file");
        return 0;
    }

    fseek(file, 0, SEEK_END);
    handler->size = ftell(file);
    fseek(file, 0, SEEK_SET);

    handler->content = malloc(handler->size + 1);
    fread(handler->content, 1, handler->size, file);
    handler->content[handler->size] = '\0';
    fclose(file);

    // Simple UTF-8 decode (assume valid); detect BOM
    if (handler->size >= 3 && handler->content[0] == 0xEF && handler->content[1] == 0xBB && handler->content[2] == 0xBF) {
        strcpy(handler->properties.bom_presence, "Yes (Non-standard)");
        // Shift content past BOM
        memmove(handler->content, handler->content + 3, handler->size - 3);
        handler->size -= 3;
        handler->content[handler->size] = '\0';
    } else {
        strcpy(handler->properties.bom_presence, "No - Standard");
    }

    detect_properties(handler);
    print_properties(handler);

    return 1;
}

void detect_properties(GDScriptHandler* handler) {
    // Detect line endings
    if (strstr(handler->content, "\r\n")) {
        strcpy(handler->properties.detected_line_endings, "CRLF (\\r\\n) - Non-standard");
    } else {
        strcpy(handler->properties.detected_line_endings, "LF (\\n) - Standard");
    }

    // Detect indentation (simple: find first indented line)
    char* indent_start = strstr(handler->content, "\n ");
    if (!indent_start) indent_start = strstr(handler->content, "\n\t");
    if (indent_start && *(indent_start + 2) == '\t') {
        strcpy(handler->properties.detected_indentation, "Tabs (\\t) - Standard");
    } else if (indent_start) {
        strcpy(handler->properties.detected_indentation, "Spaces - Non-standard");
    } else {
        strcpy(handler->properties.detected_indentation, "None detected");
    }

    if (handler->content[handler->size - 1] != '\n') {
        strcpy(handler->properties.file_termination_detected, "Non-standard");
    } else {
        strcpy(handler->properties.file_termination_detected, "Single newline - Standard");
    }
}

void print_properties(GDScriptHandler* handler) {
    printf("GDScript File Properties:\n");
    printf("EXTENSION: %s\n", handler->properties.extension);
    printf("FORMAT TYPE: %s\n", handler->properties.format_type);
    printf("ENCODING: %s\n", handler->properties.encoding);
    printf("LINE ENDINGS: %s\n", handler->properties.line_endings);
    printf("INDENTATION: %s\n", handler->properties.indentation);
    printf("FILE TERMINATION: %s\n", handler->properties.file_termination);
    printf("CHARACTER SET: %s\n", handler->properties.character_set);
    printf("MIME TYPE: %s\n", handler->properties.mime_type);
    printf("BOM PRESENCE: %s\n", handler->properties.bom_presence);
    printf("DETECTED LINE ENDINGS: %s\n", handler->properties.detected_line_endings);
    printf("DETECTED INDENTATION: %s\n", handler->properties.detected_indentation);
    printf("FILE TERMINATION DETECTED: %s\n", handler->properties.file_termination_detected);
    struct stat st;
    stat(handler->filepath, &st);
    printf("FILE SIZE: %ld bytes\n", (long)st.st_size);
    printf("\nRAW CONTENT PREVIEW:\n");
    handler->content[500] = '\0'; // Truncate for preview
    printf("%s...\n", handler->content);
}

void write_file(GDScriptHandler* handler, char* output_path) {
    // Normalize: remove CRLF, ensure LF end
    char* normalized = malloc(handler->size + 1);
    char* src = handler->content;
    char* dst = normalized;
    while (*src) {
        if (src[0] == '\r' && src[1] == '\n') {
            *dst++ = '\n';
            src += 2;
        } else {
            *dst++ = *src++;
        }
    }
    *dst = '\0';

    if (normalized[handler->size - 1] != '\n') {
        strcat(normalized, "\n");
    }

    FILE* out = fopen(output_path ? output_path : handler->filepath, "wb");
    if (out) {
        fwrite(normalized, 1, strlen(normalized), out);
        fclose(out);
        printf("File written successfully to %s\n", output_path ? output_path : handler->filepath);
    } else {
        perror("Error writing file");
    }

    free(normalized);
}

void free_handler(GDScriptHandler* handler) {
    free(handler->content);
    free(handler->filepath);
    free(handler);
}

int main(int argc, char* argv[]) {
    if (argc < 2) {
        printf("Usage: %s <file.gd>\n", argv[0]);
        return 1;
    }

    GDScriptHandler* handler = malloc(sizeof(GDScriptHandler));
    handler->filepath = strdup(argv[1]);
    init_properties(handler);

    if (read_and_decode(handler)) {
        write_file(handler, NULL); // Write back to same file
    }

    free_handler(handler);
    return 0;
}