diff options
Diffstat (limited to 'pngmeta/altwriter.go')
-rw-r--r-- | pngmeta/altwriter.go | 133 |
1 files changed, 133 insertions, 0 deletions
diff --git a/pngmeta/altwriter.go b/pngmeta/altwriter.go new file mode 100644 index 0000000..206b563 --- /dev/null +++ b/pngmeta/altwriter.go @@ -0,0 +1,133 @@ +package pngmeta + +import ( + "bytes" + "gf-lt/models" + "encoding/base64" + "encoding/binary" + "encoding/json" + "errors" + "fmt" + "hash/crc32" + "io" + "os" +) + +const ( + pngHeader = "\x89PNG\r\n\x1a\n" + textChunkType = "tEXt" +) + +// WriteToPng embeds the metadata into the specified PNG file and writes the result to outfile. +func WriteToPng(metadata *models.CharCardSpec, sourcePath, outfile string) error { + pngData, err := os.ReadFile(sourcePath) + if err != nil { + return err + } + jsonData, err := json.Marshal(metadata) + if err != nil { + return err + } + base64Data := base64.StdEncoding.EncodeToString(jsonData) + embedData := PngEmbed{ + Key: "gf-lt", // Replace with appropriate key constant + Value: base64Data, + } + var outputBuffer bytes.Buffer + if _, err := outputBuffer.Write([]byte(pngHeader)); err != nil { + return err + } + chunks, iend, err := processChunks(pngData[8:]) + if err != nil { + return err + } + for _, chunk := range chunks { + outputBuffer.Write(chunk) + } + newChunk, err := createTextChunk(embedData) + if err != nil { + return err + } + outputBuffer.Write(newChunk) + outputBuffer.Write(iend) + return os.WriteFile(outfile, outputBuffer.Bytes(), 0666) +} + +// processChunks extracts non-tEXt chunks and locates the IEND chunk +func processChunks(data []byte) ([][]byte, []byte, error) { + var ( + chunks [][]byte + iendChunk []byte + reader = bytes.NewReader(data) + ) + for { + var chunkLength uint32 + if err := binary.Read(reader, binary.BigEndian, &chunkLength); err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, nil, fmt.Errorf("error reading chunk length: %w", err) + } + chunkType := make([]byte, 4) + if _, err := reader.Read(chunkType); err != nil { + return nil, nil, fmt.Errorf("error reading chunk type: %w", err) + } + chunkData := make([]byte, chunkLength) + if _, err := reader.Read(chunkData); err != nil { + return nil, nil, fmt.Errorf("error reading chunk data: %w", err) + } + crc := make([]byte, 4) + if _, err := reader.Read(crc); err != nil { + return nil, nil, fmt.Errorf("error reading CRC: %w", err) + } + fullChunk := bytes.NewBuffer(nil) + if err := binary.Write(fullChunk, binary.BigEndian, chunkLength); err != nil { + return nil, nil, fmt.Errorf("error writing chunk length: %w", err) + } + if _, err := fullChunk.Write(chunkType); err != nil { + return nil, nil, fmt.Errorf("error writing chunk type: %w", err) + } + if _, err := fullChunk.Write(chunkData); err != nil { + return nil, nil, fmt.Errorf("error writing chunk data: %w", err) + } + if _, err := fullChunk.Write(crc); err != nil { + return nil, nil, fmt.Errorf("error writing CRC: %w", err) + } + switch string(chunkType) { + case "IEND": + iendChunk = fullChunk.Bytes() + return chunks, iendChunk, nil + case textChunkType: + continue // Skip existing tEXt chunks + default: + chunks = append(chunks, fullChunk.Bytes()) + } + } + return nil, nil, errors.New("IEND chunk not found") +} + +// createTextChunk generates a valid tEXt chunk with proper CRC +func createTextChunk(embed PngEmbed) ([]byte, error) { + content := bytes.NewBuffer(nil) + content.WriteString(embed.Key) + content.WriteByte(0) // Null separator + content.WriteString(embed.Value) + data := content.Bytes() + crc := crc32.NewIEEE() + crc.Write([]byte(textChunkType)) + crc.Write(data) + chunk := bytes.NewBuffer(nil) + if err := binary.Write(chunk, binary.BigEndian, uint32(len(data))); err != nil { + return nil, fmt.Errorf("error writing chunk length: %w", err) + } + if _, err := chunk.Write([]byte(textChunkType)); err != nil { + return nil, fmt.Errorf("error writing chunk type: %w", err) + } + if _, err := chunk.Write(data); err != nil { + return nil, fmt.Errorf("error writing chunk data: %w", err) + } + if err := binary.Write(chunk, binary.BigEndian, crc.Sum32()); err != nil { + return nil, fmt.Errorf("error writing CRC: %w", err) + } + return chunk.Bytes(), nil +} |