summaryrefslogtreecommitdiff
path: root/pngmeta/altwriter.go
diff options
context:
space:
mode:
Diffstat (limited to 'pngmeta/altwriter.go')
-rw-r--r--pngmeta/altwriter.go133
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
+}