From 1ac214e9711243aa7663a2ec5d59419a950ba2f3 Mon Sep 17 00:00:00 2001 From: cheetah Date: Sat, 5 Apr 2025 19:59:20 +0200 Subject: [PATCH] experimental w.i.p spritesheet --- commands/commands.go | 2 + commands/mpar-spritemap-dump.go | 433 +++++++++++++++++++++++++++++++ spritemap/sprite-reverse.hexproj | Bin 0 -> 70656 bytes 3 files changed, 435 insertions(+) create mode 100644 commands/mpar-spritemap-dump.go create mode 100644 spritemap/sprite-reverse.hexproj diff --git a/commands/commands.go b/commands/commands.go index f9a6224..c4c3166 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -30,6 +30,8 @@ type BaseCommand struct { MparPaxExtract MparPaxExtractCommand `command:"mpar-pax-extract" description:"extracts a single file from an pax'd mpar file"` MparPaxReplace MparPaxReplaceCommand `command:"mpar-pax-replace" description:"replaces a single file in a pax'd mpar file"` MparPaxInsert MparPaxInsertCommand `command:"mpar-pax-insert" description:"inserts a single file in a pax'd mpar file"` + + MparSpritemapDump MparSpritemapDumpCommand `command:"mpar-sm-dump" description:"dumps sprites"` } var MotoCLI BaseCommand diff --git a/commands/mpar-spritemap-dump.go b/commands/mpar-spritemap-dump.go new file mode 100644 index 0000000..ec06c5c --- /dev/null +++ b/commands/mpar-spritemap-dump.go @@ -0,0 +1,433 @@ +package commands + +import ( + "bytes" + "encoding/binary" + "encoding/hex" + "errors" + "fmt" + "io" + "os" + + "git.cheetah.cat/cheetah/moto-flash-data/flashpart" + "github.com/rs/zerolog/log" +) + +type MparSpritemapDumpCommand struct { + SourceFileName string `long:"file" short:"f" required:"true" description:"input-filename"` +} + +func writeBitmap(filename string, width, height int, pixelData []byte) error { + // BMP Header constants + fileHeaderSize := 14 + infoHeaderSize := 40 + bitsPerPixel := 24 // 3 bytes per pixel (RGB) + rowPadding := (4 - (width*3)%4) % 4 // Rows are padded to multiples of 4 bytes + imageSize := (width*3 + rowPadding) * height + fileSize := fileHeaderSize + infoHeaderSize + imageSize + + // Create BMP file + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + // BMP File Header + fileHeader := []byte{ + 'B', 'M', // Signature + 0, 0, 0, 0, // File size (placeholder) + 0, 0, // Reserved + 0, 0, // Reserved + 54, 0, 0, 0, // Offset to pixel data + } + binary.LittleEndian.PutUint32(fileHeader[2:], uint32(fileSize)) + + // BMP Info Header + infoHeader := make([]byte, infoHeaderSize) + binary.LittleEndian.PutUint32(infoHeader[0:], uint32(infoHeaderSize)) // Header size + binary.LittleEndian.PutUint32(infoHeader[4:], uint32(width)) // Image width + binary.LittleEndian.PutUint32(infoHeader[8:], uint32(height)) // Image height + binary.LittleEndian.PutUint16(infoHeader[12:], 1) // Planes + binary.LittleEndian.PutUint16(infoHeader[14:], uint16(bitsPerPixel)) // Bits per pixel + binary.LittleEndian.PutUint32(infoHeader[20:], uint32(imageSize)) // Image size + + // Write headers to file + if _, err := file.Write(fileHeader); err != nil { + return err + } + if _, err := file.Write(infoHeader); err != nil { + return err + } + + // Write pixel data with row padding + for y := height - 1; y >= 0; y-- { // BMP rows are bottom to top + rowStart := y * width * 3 + rowEnd := rowStart + width*3 + row := pixelData[rowStart:rowEnd] + + // Write row data + if _, err := file.Write(row); err != nil { + return err + } + + // Write padding + padding := make([]byte, rowPadding) + if _, err := file.Write(padding); err != nil { + return err + } + } + + return nil +} + +func writeBitmapRGB565(filename string, width, height int, pixelData []uint16) error { + // BMP Header constants + fileHeaderSize := 14 + infoHeaderSize := 40 + bitsPerPixel := 16 // 16 bits per pixel for RGB565 + rowPadding := (4 - (width*2)%4) % 4 // Rows are padded to multiples of 4 bytes + imageSize := (width*2 + rowPadding) * height + fileSize := fileHeaderSize + infoHeaderSize + imageSize + + // Create BMP file + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + // BMP File Header + fileHeader := []byte{ + 'B', 'M', // Signature + 0, 0, 0, 0, // File size (placeholder) + 0, 0, // Reserved + 0, 0, // Reserved + 54, 0, 0, 0, // Offset to pixel data + } + binary.LittleEndian.PutUint32(fileHeader[2:], uint32(fileSize)) + + // BMP Info Header + infoHeader := make([]byte, infoHeaderSize) + binary.LittleEndian.PutUint32(infoHeader[0:], uint32(infoHeaderSize)) // Header size + binary.LittleEndian.PutUint32(infoHeader[4:], uint32(width)) // Image width + binary.LittleEndian.PutUint32(infoHeader[8:], uint32(height)) // Image height + binary.LittleEndian.PutUint16(infoHeader[12:], 1) // Planes + binary.LittleEndian.PutUint16(infoHeader[14:], uint16(bitsPerPixel)) // Bits per pixel + binary.LittleEndian.PutUint32(infoHeader[20:], uint32(imageSize)) // Image size + + // Write headers to file + if _, err := file.Write(fileHeader); err != nil { + return err + } + if _, err := file.Write(infoHeader); err != nil { + return err + } + + // Write pixel data with row padding + for y := height - 1; y >= 0; y-- { // BMP rows are bottom to top + rowStart := y * width + rowEnd := rowStart + width + row := pixelData[rowStart:rowEnd] + + // Write row data + for _, pixel := range row { + if err := binary.Write(file, binary.LittleEndian, pixel); err != nil { + return err + } + } + + // Write padding + padding := make([]byte, rowPadding) + if _, err := file.Write(padding); err != nil { + return err + } + } + + return nil +} +func writeBitmapRGB565Bytes(filename string, width, height int, pixelData []byte) error { + // BMP Header constants + fileHeaderSize := 14 + infoHeaderSize := 40 + bitsPerPixel := 16 // 16 bits per pixel for RGB565 + rowPadding := (4 - (width*2)%4) % 4 // Rows are padded to multiples of 4 bytes + imageSize := len(pixelData) // (width*2 + rowPadding) * height + fileSize := fileHeaderSize + infoHeaderSize + imageSize + + // Create BMP file + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + // BMP File Header + fileHeader := []byte{ + 'B', 'M', // Signature + 0, 0, 0, 0, // File size (placeholder) + 0, 0, // Reserved + 0, 0, // Reserved + 54, 0, 0, 0, // Offset to pixel data + } + binary.LittleEndian.PutUint32(fileHeader[2:], uint32(fileSize)) + + // BMP Info Header + infoHeader := make([]byte, infoHeaderSize) + binary.LittleEndian.PutUint32(infoHeader[0:], uint32(infoHeaderSize)) // Header size + binary.LittleEndian.PutUint32(infoHeader[4:], uint32(width)) // Image width + binary.LittleEndian.PutUint32(infoHeader[8:], uint32(height)) // Image height + binary.LittleEndian.PutUint16(infoHeader[12:], 1) // Planes + binary.LittleEndian.PutUint16(infoHeader[14:], uint16(bitsPerPixel)) // Bits per pixel + binary.LittleEndian.PutUint32(infoHeader[20:], uint32(imageSize)) // Image size + + // Write headers to file + if _, err := file.Write(fileHeader); err != nil { + return err + } + if _, err := file.Write(infoHeader); err != nil { + return err + } + + // Write pixel data with row padding + for y := height - 1; y >= 0; y-- { // BMP rows are bottom to top + rowStart := y * width * 2 + rowEnd := rowStart + width*2 + row := pixelData[rowStart:rowEnd] + + // Write row data + if _, err := file.Write(row); err != nil { + return err + } + + // Write padding + padding := make([]byte, rowPadding) + if _, err := file.Write(padding); err != nil { + return err + } + } + return nil +} + +func (command *MparSpritemapDumpCommand) Execute(args []string) error { + inputFile, err := os.Open(command.SourceFileName) + if err != nil { + return err + } + defer inputFile.Close() + + inputFileStat, err := inputFile.Stat() + if err != nil { + return err + } + + log.Info().Int64("fileSize", inputFileStat.Size()).Msg("Reading raw file...") + pdata := make([]byte, inputFileStat.Size()) + _, err = io.ReadFull(inputFile, pdata) + if err != nil { + return err + } + + fpHeader := flashpart.ParseFlashPartHeader(pdata[:32]) + log.Info().Msg( + fmt.Sprintf("Media-ID: 0x%02X, Partition Size: 0x%08X / %d bytes, Partition Tag: %s", + fpHeader.MediaID, + fpHeader.TotalSize, + fpHeader.TotalSize, + fpHeader.VersionText, + ), + ) + log.Info().Msg( + fmt.Sprintf("Original-Full-Header-Hex: %s", hex.EncodeToString(pdata[:32])), + ) + + if len(pdata[32:]) != len(pdata)-32 { + return errors.New("something doesnt add up") + } + if len(pdata[32:]) != int(fpHeader.TotalSize)-32 { + log.Error().Uint32("header ts", fpHeader.TotalSize).Uint32("len pdata", uint32(len(pdata))).Msg("size mismatch") + return nil + } + + breader := bytes.NewReader(pdata[32:]) + // RGB565 + // 16 byte initial offset + // each image 13 x 13 + + { + header := make([]byte, 380) + breader.Read(header) + fmt.Println(header) + } + { + // 42 + // 338 + 42 = 380 + // + 42 = 718 = wrong + // + 12 = 730 + // + 338 = 1068 = wrong + // + 6 = 1074 + // + 338 = 1412 = wrong + // + 8 = 1420 + // + 338 = 1758 = wrong + // + 6 = 1762 + // + 338 = 2100 = wrong + // + 6 = 2106 + // + 338 = 2444 = wrong + + // + 22 = + // + 338 = 2804 = wrong + // + 6 = 2810 + // + 338 = 3148 = wrong + // + 6 = 3154 + // + 338 = 3492 = wrong + // + 6 = 3498 + // + 338 = 3836 = wrong + // + 6 = 3842 + // + 338 = 4180 = wrong + // + 6 = 4186 + // + 338 = 4524 = wrong + // + 6 = 4530 + // + 338 = 4868 = wrong + // + 6 = 4874 + // + 338 = 5212 = wrong + // + 6 = 5218 + // + 338 = 5556 = wrong + // + 6 = 5562 + // .. + // + 344 = 5906 + // + 344 = 6250 = ??? + // + 344 = 6594 = wrong + // - 6 = 6568 + // + 344 = 6912 + // + 344 = 7256 + // + 344 = 7600 = wrong + // = 7892 = ? + // = 7944 = right? + // + 292 = 8236 = right! + // + 338 = 8574 = wrong + // + 6 = 8580 + // + 344 = 8924 + // + 344 = 9268 + // + 344 = 9612 + // + 344 = 9956 + // + 344 = 10300 = wrong + // + 266 = 10566 + // + 344 = 10910 = wrong + // + 16 = 10926 + // + 344+16 = 11270 + // + 344 = 11614 + // + 344 = 11958 + // + 344 = 12302 + // + 344 = 12646 + // + 344 = 12990 + // + 344 = 13334 + // + 344 = 13678 + // + 344 = 14022 + // + 344 = 14366 + // + 344 = 14710 + // + 344 = 15054 gps + // + 344 = 15398 alarm + // + 344 = 15742 ??? = WRONG? + // 16086 = wrong + // 16774 = wrong + // 16858 check-green + // 17202 7x13px Tower + // 17390 13x13px lock + // 17734 14x13 dmo-icon + // 18130 13x12 abc-input-mode + // 18448 13x13 save-icon + // 18792 13x13 dial-icon + // + 344 + // 19136 13x13 msgicon1 + // + 344 + // 19480 13x14 alarm-clock-icon + // + 344 + // 19824 13x13 clock-icon + // + 344 + // 20168 13x13 msg-forward-read icon + // + 344 + // 20512 13x13 msg-forward-unread icon + // + 344 + // 20856 13x13 green-left-arrow + // + 344 + // 21200 13x13 blue-triangle + // + 344 + // 21544 13x13 hourglass + // + 344 + // 21888 13x13 idk-icon + // + 344 + // 22232 13x13 idk-icon + // + 344 + // 22576 13x13 idk-icon + // + 344 + // 22920 + // + 344 + // 23264 + // + 344 + // 23608 + // + 344 + // 23952 + // 24296 + // 24640 + // 24984 + // 25328 + // 25672 + // 26016 14x12 abc-input-icon + // + 370 + // 26386 14x12 normal-case + // + 370 + // 26756 14x12 normal-case + // + 370 + // 27126 14x12 numbers-input + // + 370 + // 27496 14x12 at-input + // +370 + // 27866 14x12 'I' + // +370 + // 28236 14x12 'I' UP + // +370 + // 28606 14x12 'I' UPUP + // +370 + // 28976 14x12 '2' + + PICTURE := 16 + pictoHead := make([]byte, 6) + for i := 0; i < PICTURE; i++ { + breader.Read(pictoHead) + width := binary.LittleEndian.Uint16(pictoHead[0:2]) + height := binary.LittleEndian.Uint16(pictoHead[2:4]) + magic := binary.LittleEndian.Uint16(pictoHead[4:6]) + log.Info().Uint16("width", width).Uint16("height", height).Msg("found pic") + firstPic := make([]byte, width*height*2) + breader.Read(firstPic) + fmt.Println(magic) + writeBitmapRGB565Bytes(fmt.Sprintf("debug/%02d.bmp", i), int(width), int(height), firstPic) + } + } + + // tarReader := tar.NewReader(breader) + + // for { + // header, err := tarReader.Next() + // if err == io.EOF { + // break + // } + + // if err != nil { + // log.Error().Err(err) + // break + // } + // fmt.Println(header) + + // // if header.Typeflag == tar.TypeReg && !strings.Contains(header.Name, "._") { + // // // content, err := io.ReadAll(tarReader) + // // // if err != nil { + // // // log.Error().Err(err) + // // // } + + // // fmt.Println("FILE_NAME: ", header.Name) + // // ///fmt.Println(string(content)) + // // } + // } + return nil +} diff --git a/spritemap/sprite-reverse.hexproj b/spritemap/sprite-reverse.hexproj new file mode 100644 index 0000000000000000000000000000000000000000..d8d4d7ac4c1bc8c4e73557bfc9abdc7cb9b6f3a8 GIT binary patch literal 70656 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 literal 0 HcmV?d00001