├── COPYING ├── Changelog ├── Makefile ├── README.md ├── bmp2mpp.c ├── example.s ├── mode0.s ├── mode1.s ├── mode2.s ├── mode3.s ├── mpp2bmp.c ├── mppdec.s ├── mppview.s ├── pixbuf.c ├── pixbuf.h └── release /COPYING: -------------------------------------------------------------------------------- 1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 2 | Version 2, December 2004 3 | 4 | Copyright (C) 2004 Sam Hocevar 5 | 6 | Everyone is permitted to copy and distribute verbatim or modified 7 | copies of this license document, and changing it is allowed as long 8 | as the name is changed. 9 | 10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 12 | 13 | 0. You just DO WHAT THE FUCK YOU WANT TO. 14 | -------------------------------------------------------------------------------- /Changelog: -------------------------------------------------------------------------------- 1 | Version 1.1 - 2013-12-10 2 | 3 | Fixed mode 3 overscan code, should be more stable now. 4 | Fixed 1-pixel palette shift bug which happened on some STe's in modes 0,1,3. 5 | Added optimal palette solver, for curiosity only. 6 | Various minor bug fixes. 7 | 8 | Version 1.0.1 - 2012-10-10 9 | 10 | Preserve ST palette format in raw mode instead of packed 9-bit. 11 | 12 | Version 1.0 - 2012-10-02 13 | 14 | Initial release. 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | TARGETS=bmp2mpp mpp2bmp mppview.tos 3 | 4 | AS=vasmm68k_mot 5 | ASOPT=-quiet -m68000 -Ftos 6 | CC=gcc 7 | CFLAGS=-O3 -g -Wall 8 | LDLIBS=-lm 9 | CXXFLAGS=-O3 -g -Wall 10 | 11 | all: $(TARGETS) 12 | 13 | clean: 14 | rm -f $(TARGETS) *.o 15 | 16 | bmp2mpp: bmp2mpp.o 17 | 18 | mpp2bmp: mpp2bmp.o pixbuf.o 19 | 20 | spec.o: spec.s #out.bin 21 | 22 | mppview.tos: mppview.s mppdec.s mode0.s mode1.s mode2.s mode3.s 23 | 24 | %.tos: %.s 25 | $(AS) $(ASOPT) $< -o $@ 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi palette picture for Atari ST(e) 2 | 3 | This is the official archive of the Multi palette picture (MPP) file format 4 | for Atari ST and STE computers, by Zerkman / Sector One. 5 | 6 | The archive consists of source code for generating MPP still images out of 7 | uncompressed 24-bit BMP image files, as well as for viewing such generated 8 | images on an Atari ST or STE. Additional code is also provided to convert MPP 9 | files back into BMP format. 10 | 11 | The whole source code is released without any warranty, under the WTFPL 12 | license, version 2 (see included COPYING file). For more information about 13 | this license, see http://www.wtfpl.net/ . 14 | 15 | The MPP file format actually supports different kinds of images. It has a 16 | modular-based structure, making the format easily expendable by adding source 17 | or binary plugins into the file viewer. 18 | 19 | 20 | ## Pre-defined image modes 21 | 22 | * Mode 0: 320x199, CPU based, displays 54 colors per scanline 23 | with non-uniform repartition of color change positions. 24 | * Mode 1: 320x199, CPU based, displays 48 colors per scanline 25 | with uniform repartition of color change positions. 26 | * Mode 2: 320x199, blitter based (STE only), displays 56 colors per scanline 27 | with uniform repartition of color change positions 28 | * Mode 3: 416x273, CPU based, displays 48+6 colors per scanline 29 | with overscan and non-uniform repartition of color changes. 30 | 31 | 32 | ## The MPP to BMP converter (mpp2bmp) 33 | 34 | This is a simple program written in C which enables to convert MPP images into 35 | the popular uncompressed 24-bit image bitmap format. The code is simple 36 | enough to be used as a base for importing MPP images in image editors. 37 | 38 | 39 | ## The BMP to MPP converter (bmp2mpp) 40 | 41 | This program is able to accurately convert BMP images into MPP format, 42 | thanks to a metaheuristic method to optimize the palette and pixel data of 43 | the resulting image. 44 | 45 | This is a commandline tool, but the code is designed to be easily used in a 46 | graphical application. 47 | 48 | The general command line format is : 49 | 50 | ./bmp2mpp [OPTION] file.bmp [OUTPUT] 51 | 52 | where OUTPUT is the destination file name. If omitted, the output file name 53 | is the input file name whose extension is changed to .mpp. 54 | 55 | Options are: 56 | ``` 57 | -0 No optimization 58 | -1 Optimize faster 59 | -9 Optimize better (default optimization = 3) 60 | --optimal Find the optimal solution 61 | --mode=VALUE Palette and screen mode 62 | 0: 320x199, 54 colors/scanline, ST/STE (default) 63 | 1: 320x199, 48 colors/scanline, ST/STE, uniform 64 | 2: 320x199, 56 colors/scanline, STE 65 | 3: 416x273, 48+6 colors/scanline, ST/STE, overscan 66 | --st Use 9-bit ST palette (default in modes 0, 1, 3) 67 | --ste Use 12-bit STE palette (default in mode 2) 68 | --extra Add extra palette bit (single image) 69 | --double Add extra palette bit (double image) 70 | --seed=VALUE Set random seed to VALUE (default=42) 71 | --err Display error diagnosis 72 | --raw Write raw palette instead of MPP file 73 | ``` 74 | 75 | MPP Header options: 76 | ``` 77 | --nompph Do not create MPPH extended header 78 | --title=VALUE Picture title 79 | --artist=VALUE Artist name 80 | --ripper=VALUE Ripper name 81 | --year=VALUE Year of release 82 | ``` 83 | 84 | Optimization levels from 0 to 9 specify the amount of effort the converter 85 | takes to perform the conversion. As there is no simple and straightforward 86 | way to generate the pixel and palette colors, the converter makes can use of 87 | a simulated annealing-based heuristic to try and find the best combination 88 | of values for the pixels and palette color entries. 89 | At level 0, only a very simple and fast greedy heuristic is used to generate 90 | an initial solution, which in general is of sub-satisfactory quality. 91 | Levels 1 to 9 enable the optimization heuristic to optimize that initial 92 | solution. The solution quality is expressed in terms of "error penalty" by 93 | the encoder. The lower the error level, the better the quality of the 94 | converted image. An error value of 0 would correspond to a conversion result 95 | for which every pixel is assigned the exact required color in the 96 | destination image. In practice, it is generally not the case, but optimized 97 | conversions usually show very little difference between the converted image 98 | and the original one. 99 | The `--optimal` option uses a branch-and-bound search method to find an 100 | optimal solution, ie. a conversion for which there is no possible better 101 | solution in terms of error value. It is more efficient when used in 102 | combination with the optimization heuristic with a high optimization level. 103 | This method may be used for curiosity purposes only, as the heuristic with a 104 | high optimization level already produces solution of similar if not equal 105 | quality. 106 | The encoder also outputs a quality gain ratio, which corresponds to the 107 | error penalty generated for the initial solution (using the greedy heuritic) 108 | divided by the penalty for the optimized solution. 109 | 110 | The extra palette bit (`--extra` option) mode must always be combined with STE 111 | mode. This enables palettes of 29791 possible colors. In practice, such 112 | 15-bit palettes are converted by the viewer into two alternating 12-bit 113 | palettes which are alternated at each screen refresh. 114 | 115 | The double image mode (`--double` option) is an extension of the extra palette 116 | bit mode. In this case, two 12-bit images are generated, so when they are 117 | displayed alternatively, this enables to simulate 29791 possible colors. The 118 | difference is that in the extra palette bit mode specific palette entries 119 | are used for colors with the extra bit set. In the double image mode, 120 | palette entries do encode 12-bit images, expanding the possibility to use 121 | palette entries for more various color shades. The major drawback in this 122 | case is the size of the generated image file, which becomes as large as two 123 | individual 12-bit images. 124 | 125 | Other options are for debugging only, or for users who know what they are doing. 126 | 127 | 128 | ## The MPP viewer (MPPVIEW.TTP) 129 | 130 | The provided image viewer has been written in 68000 assembly language without 131 | many features. It is basically a proof-of-concept tool, but it still can 132 | be used to view single files (by providing the file name in command line), 133 | or a slide show of all .MPP files in the current directory if no command line 134 | argument is provided, or if the program is renamed to .TOS. 135 | 136 | It is designed in a modular way, enabling easy additions of new image 137 | subformats. 138 | 139 | The viewer is responsible for displaying the pictures by alternating the 140 | palettes and/or images to simulate extra palette precision, depending on the 141 | encoding of the image files. 142 | 143 | Note that on the ST, STE palette mode (12-bit) is treated as a 9-bit palette 144 | mode with an extra bit of precision in each color component. Such images are 145 | displayed by simulating that extra bit with the palette flipping method. STE 146 | images with the extra palette bit (15-bit) are displayed on ST with the 147 | extra bit ignored. 148 | 149 | 150 | ## MPP decoding and displaying library (MPPDEC.S) 151 | 152 | This library is useful in user code to decode and display MPP 153 | image files from memory. 154 | 155 | The user code must allocate sufficient 156 | memory space for storing the corresponding image(s) and palette 157 | data. Memory space depends on the number of images (and palettes) 158 | in the file, and if a single palette has to be split in two 159 | (extended 15-bit palette on the STe, or STe palette on the ST). 160 | 161 | 162 | ## The MPP file format 163 | 164 | An MPP file consists of a 12-byte header providing the image encoding, palette 165 | format and number of images. The header format is the following. 166 | 167 | ``` 168 | 3 bytes : the three "MPP" ASCII characters 169 | 1 byte : pre-defined image mode (possible values: 0-3, other values reserved) 170 | 1 byte : flags 171 | bit 0: STE palette (12-bit, otherwise 9-bit) 172 | bit 1: extra palette bit (only in STE palette, extends the palette to 15-bit) 173 | bit 2: double image (required on very colorful images) 174 | bits 3-7: reserved, currently always zero. 175 | 3 bytes : reserved, currently always zero. 176 | 4 bytes : extra_len : length of extra header data. 177 | ``` 178 | 179 | If `extra_len` is different from zero, then a block of `extra_len` bytes follows. 180 | The `extra_len` value must be an even number. 181 | 182 | Then follows the palette data. The length of this block depends on the image 183 | mode and flags. Its value (in bits) is the number of bits per palette entry 184 | multiplied by the total number of palette entries. The resulting value is 185 | rounded to the smallest multiple of 16 not less than it. To get the size in 186 | bytes you must divide the computed value by 8. Note that because of the 187 | rounding to a multiple of 16 bits, the palette size in bytes is always a 188 | multiple of 2. 189 | 190 | Finally, comes the image data. It corresponds to unpacked bitplanes of the 191 | image. The size of the image in bytes is the image width rounded to the 192 | closest higher or equal multiple of 16, multiplied by the image height, and 193 | divided by two. 194 | 195 | In the case of double image mode, follows a second palette and image pair. 196 | 197 | 198 | ### The MPP extra header information 199 | 200 | If the `extra_len` header value is not zero, then a MPPH block follows. It 201 | is very similar to what the SNDH header information is for Atari SND files. 202 | It consists of the four `MPPH` characters, a list of tag/value pairs, and a 203 | final even-aligned `HPPM` four-character string. 204 | 205 | Below follows the list of the different supported tags. 206 | The order of the tags is not important. 207 | 208 | ``` 209 | ;------------------------------------------------------------------------------ 210 | ; TAG Description Example Termination 211 | ;------------------------------------------------------------------------------ 212 | ; TITL Title of Picture dc.b 'TITL','The Persistence of Memory',0 0 (Null) 213 | ; ARTT Artist Name dc.b 'ARTT','Salvador Dali',0 0 (Null) 214 | ; RIPP Ripper Name dc.b 'RIPP','Me the hacker',0 0 (Null) 215 | ; CONV Converter Name dc.b 'CONV','Me the converter',0 0 (Null) 216 | ; YEAR Year of release dc.b '1931',0 0 (Null) 217 | ; HPPM End of Header dc.b 'HPPM' None 218 | ; Must be on an EVEN boundary 219 | ``` 220 | -------------------------------------------------------------------------------- /bmp2mpp.c: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------- 2 | BMP to MPP file converter. 3 | by Zerkman / Sector One 4 | ------------------------------------------------------------------- */ 5 | 6 | /* Copyright (c) 2012-2025 Francois Galea 7 | * This program is free software. It comes without any warranty, to 8 | * the extent permitted by applicable law. You can redistribute it 9 | * and/or modify it under the terms of the Do What The Fuck You Want 10 | * To Public License, Version 2, as published by Sam Hocevar. See 11 | * the COPYING file or http://www.wtfpl.net/ for more details. */ 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #define MAX_NCOLORS 64 22 | #define MAX_WIDTH 416 23 | #define MAX_XDELTA 160 24 | 25 | #define DEFAULT_OPT_LEVEL 3 26 | #define ANNEAL_INIT_T_RATIO 1 27 | 28 | void 29 | __mem_error(const void *ptr, const char *name, const char* file, int line) { 30 | if (!ptr) { 31 | fprintf(stderr, "%s:%d:allocation for pointer `%s' failed\n", 32 | file, line, name); 33 | exit(1); 34 | } 35 | } 36 | 37 | #define MEM_ERROR(ptr) __mem_error(ptr, #ptr, __FILE__, __LINE__) 38 | 39 | typedef struct { 40 | int id; 41 | int ncolors; 42 | int nfixed; 43 | int x0; 44 | int (*xinc)(int); 45 | int xdelta; 46 | int width; 47 | int height; 48 | } Mode; 49 | 50 | static int xinc0(int c) { return ((c==15)?88:((c==31)?12:((c==37)?100:4))); } 51 | static int xinc1(int c) { return (((c)&1)?16:4); } 52 | static int xinc2(int c) { return 8; } 53 | static int xinc3(int c) { return ((c==15)?112:((c==31)?12:((c==37)?100:4))); } 54 | 55 | Mode modes[4] = { 56 | { 0, 54, 0, 32, xinc0, 148, 320, 200 }, 57 | { 1, 48, 0, 8, xinc1, 160, 320, 200 }, 58 | { 2, 64, 0, 4, xinc2, 128, 320, 200 }, 59 | { 3, 54, 6, 68, xinc3, 160, 416, 276 }, 60 | }; 61 | 62 | #define IMAX(x) (16-((x>0)&&(first[x-1]!=first[x]))) 63 | 64 | #define STR_EX(x) #x 65 | #define STR(x) STR_EX(x) 66 | 67 | #define EXTC(a,s) ((a>>s)&0x1f) 68 | 69 | static void write16(void *ptr, unsigned short x) { 70 | unsigned char *p = ptr; 71 | p[0] = x>>8; 72 | p[1] = x; 73 | } 74 | 75 | static void write32(void *ptr, unsigned int x) { 76 | unsigned char *p = ptr; 77 | p[0] = x>>24; 78 | p[1] = x>>16; 79 | p[2] = x>>8; 80 | p[3] = x; 81 | } 82 | 83 | static int cdist(unsigned short a, unsigned short b) { 84 | int dr = EXTC(a,10) - EXTC(b,10); 85 | int dg = EXTC(a,5) - EXTC(b,5); 86 | int db = EXTC(a,0) - EXTC(b,0); 87 | /* if (dr<0) dr = -dr; 88 | if (dg<0) dg = -dg; 89 | if (db<0) db = -db; 90 | int d = dr; 91 | if (dg>d) d = dg; 92 | if (db>d) d = db; 93 | return d*(dr+dg+db); */ 94 | return dr*dr+dg*dg+db*db; 95 | /* return dr*19+dg*55+db*26; */ 96 | } 97 | 98 | int greedy(unsigned char *ppix, unsigned short *ppal, const Mode *mode, 99 | const unsigned short *line, int special) 100 | { 101 | static int first[MAX_WIDTH]; 102 | static int init = 0; 103 | int i, j, x; 104 | int cost = 0; 105 | 106 | if (!init) { 107 | int nx = mode->x0; 108 | int idx = 0; 109 | int c = 0; 110 | for (x=0; xwidth; ++x) { 111 | if (x == nx) { 112 | ++idx; 113 | nx += mode->xinc(c); 114 | ++c; 115 | } 116 | first[x] = idx; 117 | } 118 | init=1; 119 | } 120 | 121 | if (special) { 122 | memset(ppal+mode->nfixed, -1, (12-mode->nfixed)*sizeof(unsigned short)); 123 | memset(ppal+16, -1, (mode->ncolors-16)*sizeof(unsigned short)); 124 | } else 125 | memset(ppal+mode->nfixed, -1, (mode->ncolors-mode->nfixed)*sizeof(unsigned short)); 126 | memset(ppix, -1, mode->width); 127 | if (mode->id != 3) 128 | ppal[0] = 0; 129 | ppal[(mode->ncolors-1) & -16] = 0; 130 | 131 | for (i=0; iwidth; ++i) { 132 | int jmax, idx; 133 | unsigned short *p, c; 134 | x = ((i*4)+(i/(mode->width/4))+1)%mode->width; 135 | jmax = IMAX(x); 136 | p = ppal + first[x]; 137 | c = line[x]; 138 | idx = -1; 139 | for (j=0; jnfixed; incolors+mode->nfixed; ++i) 169 | if (ppal[i] == 0xffff) 170 | ppal[i] = 0; 171 | return cost; 172 | } 173 | 174 | struct instance { 175 | /* instance-independent data */ 176 | int nc; 177 | int xmax[MAX_NCOLORS]; 178 | int first[MAX_WIDTH]; 179 | 180 | /* variable data */ 181 | const unsigned short *line; 182 | }; 183 | 184 | struct solution { 185 | unsigned short pal[MAX_NCOLORS]; 186 | unsigned char pix[MAX_WIDTH]; 187 | int eval; 188 | }; 189 | 190 | struct bbstr { 191 | const Mode *mode; 192 | 193 | /* problem and solution pointers */ 194 | unsigned char *ppix; 195 | unsigned short *ppal; 196 | 197 | /* internal data */ 198 | struct instance inst; 199 | 200 | /* current solution */ 201 | struct solution sol; 202 | int bound; 203 | int ttl; 204 | }; 205 | 206 | void instance_init(struct instance *inst, const Mode *mode) { 207 | int i, x; 208 | int nx = mode->x0; 209 | int nc = 0; 210 | int c = 0; 211 | 212 | for (x=0; xwidth; ++x) { 213 | if (x == nx) { 214 | inst->xmax[nc] = x; 215 | ++nc; 216 | nx += mode->xinc(c); 217 | ++c; 218 | } 219 | inst->first[x] = nc; 220 | } 221 | for (i=0; i<16; ++i) 222 | inst->xmax[nc++] = x; 223 | inst->nc = nc; 224 | } 225 | 226 | void solution_init(struct solution *sol, const Mode *mode) { 227 | memset(sol->pal, -1, mode->ncolors*sizeof(unsigned short)); 228 | memset(sol->pix, -1, mode->width*sizeof(unsigned char)); 229 | sol->eval = 0; 230 | } 231 | 232 | /* Compute distance array of a solution */ 233 | void solution_computedist(const Mode *mode, int *dist, 234 | struct solution *sol, const struct instance *inst) 235 | { 236 | int x; 237 | sol->eval = 0; 238 | for (x=0; xwidth; ++x) { 239 | unsigned short *pal = sol->pal + inst->first[x]; 240 | unsigned short c = inst->line[x]; 241 | unsigned short ca = pal[(sol->pix[x]-inst->first[x]) & 0xf]; 242 | dist[x] = cdist(c, ca); 243 | sol->eval += dist[x]; 244 | } 245 | } 246 | 247 | /* Cost of changing palette entry palidx to color col */ 248 | static int compute_delta(const Mode *mode, const struct solution *sol, 249 | const struct instance *inst, int *dist, int palidx, unsigned short col) 250 | { 251 | int delta = 0; 252 | int x0 = (palidx<16)?0:inst->xmax[palidx-16]; 253 | int x1 = inst->xmax[palidx]; 254 | int x, i; 255 | const int *first = inst->first; 256 | for (x=x0; xpix[x] == (palidx&0xf) || dist[x] != 0) { 258 | /* cost of changing color palidx to col for pixel x */ 259 | int f = first[x]; 260 | unsigned short c = inst->line[x]; 261 | int cost = cdist(c, col); 262 | int imax = IMAX(x); 263 | for (i=0; ipal[idx]); 268 | if (d < cost) 269 | cost = d; 270 | } 271 | delta += cost - dist[x]; 272 | } 273 | } 274 | return delta; 275 | } 276 | 277 | /* Change palette entry palidx to color col */ 278 | static void apply_delta(struct solution *sol, const struct instance *inst, 279 | int dist[MAX_WIDTH], int palidx, unsigned short col) 280 | { 281 | int x0 = (palidx<16)?0:inst->xmax[palidx-16]; 282 | int x1 = inst->xmax[palidx]; 283 | int x, i; 284 | const int *first = inst->first; 285 | sol->pal[palidx] = col; 286 | palidx &= 0xf; 287 | for (x=x0; xpix[x] == palidx || dist[x] != 0) { 289 | /* minimize cost of changing color palidx to col for pixel x */ 290 | int f = first[x]; 291 | unsigned short c = inst->line[x]; 292 | int cost = cdist(c, sol->pal[f]); 293 | int best = 0; 294 | int imax = IMAX(x); 295 | for (i=1; ipal[i+f]); 297 | if (d < cost) { 298 | cost = d; 299 | best = i; 300 | } 301 | } 302 | sol->pix[x] = (best+f)&0xf; 303 | sol->eval += cost - dist[x]; 304 | dist[x] = cost; 305 | } 306 | } 307 | } 308 | 309 | 310 | int anneal(unsigned char *ppix, unsigned short *ppal, const Mode *mode, 311 | const unsigned short *line, int bnd, int opt_level, int special) 312 | { 313 | struct instance inst; 314 | struct solution sol, best; 315 | int dist[MAX_WIDTH]; 316 | int colorcnt[MAX_NCOLORS]; 317 | unsigned short colors[MAX_NCOLORS][MAX_XDELTA]; 318 | int colornum[MAX_NCOLORS]; 319 | int i, j; 320 | int orig_eval, worst_eval; 321 | int threshold = opt_level * opt_level * mode->ncolors / 16; 322 | int nval; 323 | double t; 324 | int cool_cnt; 325 | 326 | instance_init(&inst, mode); 327 | inst.line = line; 328 | memcpy(&sol.pal, ppal, mode->ncolors*sizeof(unsigned short)); 329 | memcpy(&sol.pix, ppix, mode->width); 330 | solution_computedist(mode, dist, &sol, &inst); 331 | best = sol; 332 | orig_eval = best.eval; 333 | worst_eval = orig_eval; 334 | 335 | /* init color arrays */ 336 | nval = 0; 337 | for (i=mode->nfixed; i nval) 355 | nval = nh; 356 | } 357 | for (i=0; infixed; ++i) { 358 | colors[i][0] = sol.pal[i]; 359 | colorcnt[i] = 1; 360 | } 361 | if (special) for(i=12; i<16; ++i) { 362 | colors[i][0] = sol.pal[i]; 363 | colorcnt[i] = 1; 364 | } 365 | for (i=0; iid != 3) { 373 | /* Force left border color to black in non-overscan modes */ 374 | colors[0][0] = 0; 375 | colorcnt[0] = 1; 376 | colornum[0] = 0; 377 | } 378 | colors[(mode->ncolors-1) & -16][0] = 0; 379 | colorcnt[(mode->ncolors-1) & -16] = 1; 380 | colornum[(mode->ncolors-1) & -16] = 0; 381 | 382 | /* simulated annealing loop */ 383 | t = ANNEAL_INIT_T_RATIO*sol.eval; 384 | cool_cnt = 0; 385 | for (;;) { 386 | int palidx, num, delta; 387 | do { 388 | palidx = rand()%inst.nc; 389 | } while (colorcnt[palidx] < 2); 390 | num = rand()%(colorcnt[palidx]-1); 391 | num += num >= colornum[palidx]; 392 | 393 | delta = compute_delta(mode, &sol, &inst, dist, palidx, colors[palidx][num]); 394 | 395 | if (rand()*(1./RAND_MAX) < exp(-delta/t)) { 396 | /* update solution */ 397 | 398 | apply_delta(&sol, &inst, dist, palidx, colors[palidx][num]); 399 | 400 | if (sol.eval < best.eval) { 401 | best = sol; 402 | if (sol.eval == 0) 403 | break; 404 | } 405 | } else { 406 | int eval = sol.eval + delta; 407 | if (eval > worst_eval) 408 | worst_eval = eval; 409 | } 410 | ++cool_cnt; 411 | if (cool_cnt == threshold) { 412 | double tf = 0.02*(worst_eval-best.eval)/(nval*.693-3); 413 | t = t / (1. + 100.*t/(worst_eval+1)); 414 | if (t < tf) 415 | break; 416 | cool_cnt = 0; 417 | } 418 | } 419 | 420 | if (best.eval < orig_eval) { 421 | memcpy(ppal, &best.pal, mode->ncolors*sizeof(unsigned short)); 422 | memcpy(ppix, &best.pix, mode->width); 423 | return best.eval; 424 | } 425 | return orig_eval; 426 | } 427 | 428 | /* cost of not assigning pixel x, assuming there are no colors available */ 429 | static int cost(struct bbstr *st, int x) { 430 | unsigned short *p, c; 431 | int i; 432 | int best; 433 | if (st->sol.pix[x] != 0xff) 434 | return 0; 435 | c = st->inst.line[x]; 436 | p = st->sol.pal + st->inst.first[x]; 437 | best = cdist(p[0], c); 438 | for (i=1; i<16; ++i) { 439 | int v = cdist(p[i], c); 440 | if (v < best) 441 | best = v; 442 | } 443 | return best; 444 | } 445 | 446 | void sol(struct bbstr *st) { 447 | if (st->sol.eval >= st->bound) { 448 | printf("warning\n"); 449 | return; 450 | } 451 | memcpy(st->ppix, st->sol.pix, st->mode->width); 452 | memcpy(st->ppal, st->sol.pal, (st->mode->ncolors-st->mode->nfixed)*sizeof(unsigned short)); 453 | st->ttl = 10000 * st->sol.eval; 454 | st->bound = st->sol.eval; 455 | } 456 | 457 | void child(struct bbstr *st, int idx) 458 | { 459 | unsigned char have[32768/8]; 460 | struct { unsigned int c, eval; } list[MAX_XDELTA], tmp; 461 | int xs[MAX_XDELTA]; 462 | int i, x, nxs; 463 | 464 | /* Establish the list of candidate colors */ 465 | int x0 = (idx<16)?0:st->inst.xmax[idx-16]; 466 | int x1 = st->inst.xmax[idx]; 467 | int size = 0; 468 | list[0].c = 0; 469 | list[0].eval = 0; 470 | if (idx == 0 || idx == ((st->mode->ncolors-1) & -16)) { 471 | size = 1; 472 | } 473 | else { 474 | memset(have, 0, sizeof(have)); 475 | for (x=x0; xsol.pix[x] == 0xff) { 477 | unsigned short c = st->inst.line[x]; 478 | int mask = 1<<(c&7); 479 | int pos = c/8; 480 | if (!(have[pos]&mask)) { 481 | have[pos] |= mask; 482 | list[size].c = c; 483 | list[size].eval = 0; 484 | ++size; 485 | } 486 | } 487 | } 488 | } 489 | 490 | if (idx >= 15) { 491 | int j; 492 | /* evaluate the node */ 493 | nxs = 0; 494 | for (i=0; isol.eval; 496 | for (x=x0; xinst.xmax[idx-15]; ++x) 497 | if (st->inst.line[x] != list[i].c) 498 | list[i].eval += cost(st, x); 499 | } 500 | /* Sort the candidate colors */ 501 | for (i=0; iinst.nc-1) 513 | sol(st); 514 | else 515 | child(st, idx+1); 516 | return; 517 | } 518 | 519 | /* For all candidate colors, try solving with that color fixed */ 520 | nxs = 0; 521 | for (i=0; isol.eval; 524 | if (--st->ttl == 0) 525 | return; 526 | /* if eval < bound, explore the child node */ 527 | if (list[i].eval < st->bound) { 528 | st->sol.pal[idx] = c; 529 | for (x=x0; xinst.line[x] == c && st->sol.pix[x] == 0xff) { 531 | xs[nxs++] = x; 532 | st->sol.pix[x] = idx&0xf; 533 | } 534 | } 535 | st->sol.eval = list[i].eval; 536 | if (idx == st->inst.nc-1) 537 | sol(st); 538 | else 539 | child(st, idx+1); 540 | /* restore the previous node status */ 541 | st->sol.eval = eval; 542 | while (nxs) 543 | st->sol.pix[xs[--nxs]] = 0xff; 544 | st->sol.pal[idx] = -1; 545 | 546 | if (st->bound == eval || st->ttl == 0) 547 | return; 548 | } 549 | } 550 | } 551 | 552 | int exact(unsigned char *ppix, unsigned short *ppal, const Mode *mode, 553 | const unsigned short *line, int bnd) 554 | { 555 | struct bbstr str, *st = &str; 556 | int i, x; 557 | 558 | st->mode = mode; 559 | st->ppix = ppix; 560 | st->ppal = ppal; 561 | instance_init(&st->inst, mode); 562 | st->inst.line = line; 563 | solution_init(&st->sol, mode); 564 | 565 | st->bound = bnd; 566 | st->ttl = 10000000; 567 | 568 | child(st, 0); 569 | if (st->bound != 0) { 570 | for (x=0; xwidth; ++x) { 571 | if (st->ppix[x] == 0xff) { 572 | unsigned short *p = st->ppal + st->inst.first[x]; 573 | unsigned short c = st->inst.line[x]; 574 | int dist = cdist(c, p[0]); 575 | int idx = 0; 576 | for (i=1; i<16; ++i) { 577 | int d = cdist(c, p[i]); 578 | if (d < dist) { 579 | dist = d; 580 | idx = i; 581 | } 582 | } 583 | st->ppix[x] = (idx + st->inst.first[x]) & 0xf; 584 | } 585 | } 586 | } 587 | return st->bound; 588 | } 589 | 590 | int usage(const char *progname) { 591 | fprintf(stderr, 592 | "usage: %s [OPTION] file.bmp [OUTPUT]\n\n" 593 | "Options are:\n" 594 | " -0\t\t\tNo optimization\n" 595 | " -1\t\t\tOptimize faster\n" 596 | " -9\t\t\tOptimize better (default optimization = "STR(DEFAULT_OPT_LEVEL)")\n" 597 | " --optimal\t\tFind the optimal solution\n" 598 | " --mode=VALUE\t\tPalette and screen mode\n" 599 | "\t\t\t0: 320x200, 54 colors/scanline, ST/STE (default)\n" 600 | "\t\t\t1: 320x200, 48 colors/scanline, ST/STE, uniform\n" 601 | "\t\t\t2: 320x200, 56 colors/scanline, STE\n" 602 | "\t\t\t3: 416x276, 48+6 colors/scanline, ST/STE, overscan\n" 603 | " --st\t\t\tUse 9-bit ST palette (default in modes 0, 1, 3)\n" 604 | " --ste\t\t\tUse 12-bit STE palette (default in mode 2)\n" 605 | " --extra\t\tAdd extra palette bit (single image)\n" 606 | " --double\t\tAdd extra palette bit (double image)\n" 607 | " --seed=VALUE\t\tSet random seed to VALUE (default=42)\n" 608 | " --err\t\t\tDisplay error diagnosis\n" 609 | " --raw\t\t\tWrite raw data instead of MPP file\n" 610 | "\nMPP Header options:\n" 611 | " --nompph\t\tDo not create MPPH extended header\n" 612 | " --title=VALUE\t\tPicture title\n" 613 | " --artist=VALUE\tArtist name\n" 614 | " --ripper=VALUE\tRipper name\n" 615 | " --year=VALUE\t\tYear of release\n" 616 | , progname); 617 | return 1; 618 | } 619 | 620 | int file_error(const char *progname, const char *file, const char *msg) { 621 | fprintf(stderr, "%s: %s: %s\n", progname, file, msg); 622 | return 1; 623 | } 624 | 625 | static int readle2(const char *ptr) { 626 | const signed char *sptr = (const signed char *)ptr; 627 | const unsigned char *uptr = (const unsigned char *)ptr; 628 | int x; 629 | x = uptr[0]; 630 | x |= sptr[1]<<8; 631 | return x; 632 | } 633 | 634 | static int readle4(const char *ptr) { 635 | const signed char *sptr = (const signed char *)ptr; 636 | const unsigned char *uptr = (const unsigned char *)ptr; 637 | int x; 638 | x = uptr[0]; 639 | x |= uptr[1]<<8; 640 | x |= uptr[2]<<16; 641 | x |= sptr[3]<<24; 642 | return x; 643 | } 644 | 645 | 646 | void convert(FILE *fd, const unsigned char *bmp, const Mode *mode, int bits, 647 | int flick, int opt, int optimal, int raw_palette, int err) 648 | { 649 | unsigned char *pixels, *ppix; 650 | unsigned short *palette; 651 | int i, x, y; 652 | int penalty0 = 0, penalty1 = 0; 653 | int npal, nbits, bpp; 654 | unsigned short *bitmap, *pbitmap; 655 | unsigned int tmpcol; 656 | pixels = malloc(mode->width*mode->height); 657 | palette = malloc((mode->height+1)*(mode->ncolors-mode->nfixed)*sizeof(unsigned short)); 658 | bitmap = malloc(mode->width/4*mode->height*sizeof(unsigned short)); 659 | MEM_ERROR(pixels); 660 | MEM_ERROR(palette); 661 | MEM_ERROR(bitmap); 662 | 663 | memset(palette, 0, (mode->ncolors-mode->nfixed)*sizeof(palette[0])); 664 | for (y=0; yheight; ++y) { 665 | const unsigned char *pbmp = &bmp[mode->width*3*(mode->height-1-y)]; 666 | unsigned short *ppal = &palette[(y+1)*(mode->ncolors-mode->nfixed)-mode->nfixed]; 667 | unsigned short line[MAX_WIDTH]; 668 | int val, special = 0; 669 | 670 | for (x=0; xwidth; ++x) { 671 | int r, g, b, t; 672 | int mask = 0; 673 | if (flick && ((flick^y)&1)) 674 | mask = 1<<(7-bits); 675 | t = *pbmp++; 676 | b = t>>(8-bits); 677 | b += b < (1<>(8-bits); 680 | g += g < (1<>(8-bits); 683 | r += r < (1<width, ppal, mode, line, special); 691 | penalty0 += val; 692 | if (val && opt) { 693 | val = anneal(pixels+y*mode->width, ppal, mode, line, val, opt, special); 694 | } 695 | if (val && optimal) { 696 | val = exact(pixels+y*mode->width, ppal, mode, line, val); 697 | } 698 | penalty1 += val; 699 | if (val && err) 700 | printf("y=%d error=%d \n", y, val); 701 | if (opt) { 702 | char buf[16]; 703 | if (penalty1) 704 | sprintf(buf, "%.02f", (double)penalty0/penalty1); 705 | else 706 | strcpy(buf, "*"); 707 | printf("y=%d gain=%s \r", y, buf); 708 | fflush(stdout); 709 | } 710 | } 711 | if (opt) { 712 | char buf[16]; 713 | if (penalty1) 714 | sprintf(buf, "%.02f", (double)penalty0/penalty1); 715 | else 716 | strcpy(buf, "inf"); 717 | printf("Error penalty: initial %d, final %d. Gain ratio: %s\n", 718 | penalty0, penalty1, buf); 719 | } 720 | else 721 | printf("Error penalty: %d.\n", penalty0); 722 | 723 | // ppix = &pixels[mode->width]; 724 | ppix = pixels; 725 | pbitmap = bitmap; 726 | npal = 0; 727 | nbits = 0; 728 | bpp = 3*bits; 729 | tmpcol = 0; 730 | for (y=0; yheight; ++y) { 731 | for (x=0; xwidth; x+=16) { 732 | unsigned short b0=0, b1=0, b2=0, b3=0; 733 | for (i=0; i<16; ++i) { 734 | int c = *ppix++; 735 | b0 |= (c&1) << (15-i); 736 | b1 |= ((c&2)>>1) << (15-i); 737 | b2 |= ((c&4)>>2) << (15-i); 738 | b3 |= ((c&8)>>3) << (15-i); 739 | } 740 | write16(pbitmap+0, b0); 741 | write16(pbitmap+1, b1); 742 | write16(pbitmap+2, b2); 743 | write16(pbitmap+3, b3); 744 | pbitmap += 4; 745 | } 746 | for (x=0; x<(mode->ncolors-mode->nfixed); ++x) { 747 | unsigned short c = palette[(y+1)*(mode->ncolors-mode->nfixed)+x], ex; 748 | switch (bits) { 749 | case 3: 750 | if (raw_palette) 751 | c = ((c>>10)&0x7)<<8 | ((c>>5)&0x7)<<4 | (c&0x7); 752 | else 753 | c = ((c>>10)&0x7)<<6 | ((c>>5)&0x7)<<3 | (c&0x7); 754 | break; 755 | case 4: 756 | c = ((c>>10)&0xf)<<8 | ((c>>5)&0xf)<<4 | (c&0xf); 757 | c = ((c&0xeee)>>1) | ((c&0x111)<<3); 758 | break; 759 | case 5: 760 | ex = ((c>>10)&1)<<14 | ((c>>5)&1)<<13 | (c&1)<<12; 761 | c = ((c>>11)&0xf)<<8 | ((c>>6)&0xf)<<4 | ((c>>1)&0xf); 762 | c = ex | ((c&0xeee)>>1) | ((c&0x111)<<3); 763 | break; 764 | default: 765 | fprintf(stderr, "wrong number of bits\n"); 766 | abort(); 767 | } 768 | if (raw_palette) 769 | write16(&palette[y*(mode->ncolors-mode->nfixed)+x], c); 770 | else if (mode->id != 3 && (x == 0 || x == ((mode->ncolors-1) & -16))) 771 | continue; 772 | else if (mode->id == 2 && x >= 56) 773 | break; 774 | else { 775 | nbits += bpp; 776 | tmpcol = (tmpcol << bpp) | c; 777 | if (nbits >= 16) { 778 | nbits -= 16; 779 | write16(&palette[npal++], tmpcol >> nbits); 780 | } 781 | } 782 | } 783 | if (raw_palette && mode->id == 0) { 784 | /* specific color reordering for mode 0 */ 785 | unsigned short tmp[6]; 786 | memcpy(tmp, palette+y*mode->ncolors+48, 6*2); 787 | memmove(palette+y*mode->ncolors+22, palette+y*mode->ncolors+16, 32*2); 788 | memcpy(palette+y*mode->ncolors+16, tmp, 6*2); 789 | } 790 | } 791 | 792 | if (nbits != 0) 793 | write16(&palette[npal++], tmpcol << (16-nbits)); 794 | 795 | if (raw_palette) { 796 | fwrite(bitmap, 2, (mode->width/4)*mode->height, fd); 797 | fwrite(palette+(mode->ncolors-mode->nfixed), 2*(mode->ncolors-mode->nfixed), 798 | mode->height, fd); 799 | } 800 | else { 801 | fwrite(palette, 2, npal, fd); 802 | fwrite(bitmap, 2, (mode->width/4)*mode->height, fd); 803 | } 804 | free(bitmap); 805 | free(palette); 806 | free(pixels); 807 | } 808 | 809 | int get_year() { 810 | struct tm *tm; 811 | time_t today = time(NULL); 812 | tm = localtime(&today); 813 | return tm->tm_year + 1900; 814 | } 815 | 816 | int main(int argc, char **argv) { 817 | char header[14+20]; 818 | char outfilenamebuf[512], *p, *filename, *outfilename; 819 | FILE *fd; 820 | int i; 821 | int bits; 822 | int opt = DEFAULT_OPT_LEVEL; 823 | const Mode *mode = &modes[0]; 824 | int optimal = 0; 825 | int ste = 0; 826 | int extra = 0; 827 | int doubl = 0; 828 | int err = 0; 829 | int raw_palette = 0; 830 | int width, height, offset, bpp, compression; 831 | int randseed = 42; 832 | unsigned char *bmp; 833 | char yearbuf[8]; 834 | int mpph = 1; 835 | const char *titl = ""; 836 | const char *artt = ""; 837 | const char *ripp = ""; 838 | const char *year = yearbuf; 839 | 840 | sprintf(yearbuf, "%d", get_year()); 841 | 842 | for (i=1; argv[i] && argv[i][0] == '-'; ++i) { 843 | if (!strcmp(argv[i], "--optimal")) 844 | optimal = 1; 845 | else if (!strncmp(argv[i], "--mode=", 7)) { 846 | int val = atoi(argv[i]+7); 847 | if (val < 0 || val > 3) 848 | return usage(argv[0]); 849 | if (val == 2) 850 | ste = 1; 851 | mode = &modes[val]; 852 | } 853 | else if (!strcmp(argv[i], "--st")) 854 | ste = 0; 855 | else if (!strcmp(argv[i], "--ste")) 856 | ste = 1; 857 | else if (!strcmp(argv[i], "--extra")) 858 | extra = 1; 859 | else if (!strcmp(argv[i], "--double")) 860 | doubl = 1; 861 | else if (!strncmp(argv[i], "--seed=", 7)) 862 | randseed = atoi(argv[i]+7); 863 | else if (!strcmp(argv[i], "--err")) 864 | err = 1; 865 | else if (!strcmp(argv[i], "--raw")) 866 | raw_palette = 1; 867 | else if (strlen(argv[i]) == 2 && argv[i][1] >= '0' && argv[i][1] <= '9') 868 | opt = argv[i][1] - '0'; 869 | else if (!strcmp(argv[i], "--nompph")) 870 | mpph = 0; 871 | else if (!strncmp(argv[i], "--title=", 8)) 872 | titl = argv[i]+8; 873 | else if (!strncmp(argv[i], "--artist=", 9)) 874 | artt = argv[i]+9; 875 | else if (!strncmp(argv[i], "--ripper=", 9)) 876 | ripp = argv[i]+9; 877 | else if (!strncmp(argv[i], "--year=", 7)) 878 | year = argv[i]+7; 879 | else 880 | return usage(argv[0]); 881 | } 882 | if (argc - i < 1 || argc - i > 2) 883 | return usage(argv[0]); 884 | if (extra && doubl) { 885 | fprintf(stderr, "%s: --extra and --double cannot be used simultaneously\n", 886 | argv[0]); 887 | return 1; 888 | } 889 | if (extra && !ste) { 890 | fprintf(stderr, "%s: --extra and --st cannot be used simultaneously\n", 891 | argv[0]); 892 | return 1; 893 | } 894 | 895 | /* output file name management */ 896 | if (argc - i == 1) { 897 | strcpy(outfilenamebuf, argv[i]); 898 | p = strrchr(outfilenamebuf, '.'); 899 | if (!p) 900 | p = outfilenamebuf + strlen(outfilenamebuf); 901 | strcpy(p, raw_palette?".bin":".mpp"); 902 | outfilename = outfilenamebuf; 903 | } 904 | else 905 | outfilename = argv[i+1]; 906 | 907 | filename = argv[i]; 908 | fd = fopen(filename, "rb"); 909 | if (!fd) { 910 | perror(filename); 911 | return 1; 912 | } 913 | 914 | /* Analyze file header */ 915 | fread(header, 14+20, 1, fd); 916 | if (strncmp(header, "BM", 2)) 917 | return file_error(argv[0], filename, "must be in BMP format"); 918 | offset = readle4(header+10); 919 | width = readle4(header+18); 920 | height = readle4(header+22); 921 | bpp = readle2(header+28); 922 | compression = readle4(header+30); 923 | if (bpp != 24 || compression != 0) 924 | return file_error(argv[0], filename, "BMP format must be uncompressed 24-bit"); 925 | if (width != mode->width || height != mode->height) { 926 | char buf[256]; 927 | sprintf(buf, "image size must be %dx%d", mode->width, mode->height); 928 | return file_error(argv[0], filename, buf); 929 | } 930 | bmp = malloc(mode->width*mode->height*3); 931 | MEM_ERROR(bmp); 932 | fseek(fd, offset, SEEK_SET); 933 | fread(&bmp[0], 3, mode->width*mode->height, fd); 934 | fclose(fd); 935 | 936 | fd = fopen(outfilename, "wb"); 937 | if (fd == NULL) { 938 | perror(outfilename); 939 | return 1; 940 | } 941 | if (!raw_palette) { 942 | static char header[12] = "MPP\0\0\0\0\0\0\0\0"; 943 | header[3] = mode->id; 944 | header[4] = doubl<<2 | extra<<1 | ste; 945 | if (mpph) { 946 | static struct { const char *head, *val; } mpph_items[] = { 947 | { "TITL", NULL }, 948 | { "ARTT", NULL }, 949 | { "RIPP", NULL }, 950 | { "CONV", "bmp2mpp by Zerkman / Sector One" }, 951 | { "YEAR", NULL } 952 | }; 953 | int len, xtra = 0, all = 8; 954 | mpph_items[0].val = titl; 955 | mpph_items[1].val = artt; 956 | mpph_items[2].val = ripp; 957 | mpph_items[4].val = year; 958 | for (i = 0; i < sizeof(mpph_items)/sizeof(mpph_items[0]); ++i) { 959 | len = strlen(mpph_items[i].val) + 1; 960 | if (len > 1) 961 | all += 4 + len; 962 | } 963 | all = (all+1)&-2; 964 | write32(header+8, all); 965 | fwrite(header, 12, 1, fd); 966 | fwrite("MPPH", 4, 1, fd); 967 | for (i = 0; i < sizeof(mpph_items)/sizeof(mpph_items[0]); ++i) { 968 | len = strlen(mpph_items[i].val) + 1; 969 | if (len > 1) { 970 | xtra += len; 971 | fwrite(mpph_items[i].head, 4, 1, fd); 972 | fwrite(mpph_items[i].val, len, 1, fd); 973 | } 974 | } 975 | xtra &= 1; 976 | fwrite("\0HPPM" + (1-xtra), 4 + xtra, 1, fd); 977 | } else 978 | fwrite(header, 12, 1, fd); 979 | } 980 | srand(randseed); 981 | bits = 3 + ste + extra; 982 | if (doubl) { 983 | convert(fd, bmp, mode, bits, 1, opt, optimal, raw_palette, err); 984 | convert(fd, bmp, mode, bits, 2, opt, optimal, raw_palette, err); 985 | } 986 | else 987 | convert(fd, bmp, mode, bits, 0, opt, optimal, raw_palette, err); 988 | 989 | fclose(fd); 990 | printf("Successfully written to output file `%s'.\n", outfilename); 991 | free(bmp); 992 | 993 | return 0; 994 | } 995 | -------------------------------------------------------------------------------- /example.s: -------------------------------------------------------------------------------- 1 | ;--------------------------------------------------------------------- 2 | ; Example program to display an MPP image from memory using the mppdec library 3 | ; by Zerkman / Sector One 4 | ;--------------------------------------------------------------------- 5 | 6 | ; Copyright (c) 2012-2025 Francois Galea 7 | ; This program is free software. It comes without any warranty, to 8 | ; the extent permitted by applicable law. You can redistribute it 9 | ; and/or modify it under the terms of the Do What The Fuck You Want 10 | ; To Public License, Version 2, as published by Sam Hocevar. See 11 | ; the COPYING file or http://www.wtfpl.net/ for more details. 12 | 13 | opt p=68000 14 | 15 | nlines: equ 276 16 | lnsize: equ 230 17 | 18 | section text 19 | 20 | clr.l -(sp) 21 | move #32,-(sp) ; Super 22 | trap #1 23 | addq.l #6,sp 24 | move.l d0,-(sp) 25 | 26 | lea -128(sp),sp 27 | move.l sp,a0 28 | movem.l $ffff8240.w,d1-d7/a1 29 | movem.l d1-d7/a1,(a0) 30 | add #32,a0 31 | move.l $ffff8200.w,(a0)+ 32 | move.b $ffff820a.w,(a0)+ 33 | move.b $ffff8260.w,(a0)+ 34 | move.l $68.w,(a0)+ 35 | move.l $70.w,(a0)+ 36 | move.l $134.w,(a0)+ 37 | move.b $fffffa07.w,(a0)+ 38 | move.b $fffffa09.w,(a0)+ 39 | move.b $fffffa13.w,(a0)+ 40 | move.b $fffffa15.w,(a0)+ 41 | move.b $fffffa17.w,(a0)+ 42 | 43 | move.l sp,savesp 44 | 45 | bsr mpp_init 46 | 47 | ; *** Decode the MPP file 48 | lea imgbuf,a1 49 | lea image(pc),a0 50 | bsr mpp_decode 51 | 52 | ; *** Display image 53 | bsr mpp_setup_img 54 | 55 | ; Main loop that just waits for a press on space bar 56 | main_loop: 57 | move.b $fffffc02.w,d0 58 | cmp.b #$39,d0 59 | bne.s main_loop 60 | 61 | exit: 62 | move #$2700,sr 63 | 64 | move.l savesp,sp 65 | 66 | move.l sp,a0 67 | movem.l (a0)+,d1-d7/a1 68 | movem.l d1-d7/a1,$ffff8240.w 69 | move.l (a0)+,$ffff8200.w 70 | move.b (a0)+,$ffff820a.w 71 | move.b (a0)+,$ffff8260.w 72 | move.l (a0)+,$68.w 73 | move.l (a0)+,$70.w 74 | move.l (a0)+,$134.w 75 | move.b (a0)+,$fffffa07.w 76 | move.b (a0)+,$fffffa09.w 77 | move.b (a0)+,$fffffa13.w 78 | move.b (a0)+,$fffffa15.w 79 | move.b (a0)+,$fffffa17.w 80 | 81 | move #$2300,sr 82 | 83 | lea 128(sp),sp 84 | 85 | move #32,-(sp) 86 | trap #1 87 | addq.l #6,sp 88 | 89 | clr -(sp) 90 | trap #1 91 | 92 | 93 | include "mppdec.s" 94 | 95 | section data 96 | image: incbin "image.mpp" 97 | 98 | section bss 99 | savesp: ds.l 1 100 | imgbuf: ds.b mppstrsize+2*(lnsize*nlines+254)+2*(nlines*48*2) 101 | -------------------------------------------------------------------------------- /mode0.s: -------------------------------------------------------------------------------- 1 | ;--------------------------------------------------------------------- 2 | ; Multipalette routine. 3 | ; by Zerkman / Sector One 4 | ; mode 0: 320x200, CPU based, displays 54 colors per scanline 5 | ; with non-uniform repartition of color change positions. 6 | ;--------------------------------------------------------------------- 7 | 8 | ; Copyright (c) 2012-2025 Francois Galea 9 | ; This program is free software. It comes without any warranty, to 10 | ; the extent permitted by applicable law. You can redistribute it 11 | ; and/or modify it under the terms of the Do What The Fuck You Want 12 | ; To Public License, Version 2, as published by Sam Hocevar. See 13 | ; the COPYING file or http://www.wtfpl.net/ for more details. 14 | 15 | ; Plugin header. 16 | m0_begin: 17 | dc.w 320 ; width 18 | dc.w 200 ; height 19 | dc.w 54 ; colors per scanline 20 | dc.w 52 ; stored colors per scanline 21 | dc.w 160 ; physical screen line size in bytes 22 | dc.w 189 ; timer A data 23 | dc.w 4 ; flags: 4 calls to nextshift 24 | m0_pal: dc.l 0 ; palette address 25 | bra.w m0_init 26 | bra.w m0_palette_unpack 27 | m0_tab: bra.w m0_timera1 28 | 29 | 30 | ; Palette unpacking. 31 | ; a0: destination unpacked palette 32 | ; a5: get_color function 33 | ; d5-d7/a4-a6 : reserved for get_color function 34 | m0_palette_unpack: 35 | move #200-1,d2 ; line counter 36 | m0_pu_newline: 37 | clr (a0)+ ; set color 0 to black 38 | moveq #51,d1 ; 54 colors per line, -2 always black 39 | m0_pu_newcol: 40 | cmp #36,d1 41 | bne.s m0_no16 ; start 16th color at position 22 42 | lea 12(a0),a0 43 | m0_no16: 44 | cmp #4,d1 45 | bne.s m0_pu_no48 46 | lea -64-12(a0),a0 47 | clr (a0)+ ; set color 32 to black 48 | 49 | m0_pu_no48: 50 | jsr (a5) 51 | move d0,(a0)+ 52 | dbra d1,m0_pu_newcol 53 | lea 64(a0),a0 54 | dbra d2,m0_pu_newline 55 | rts 56 | 57 | 58 | ; Init routine. 59 | m0_init: 60 | move.b #2,$ffff820a.w 61 | clr.b $ffff8260.w 62 | 63 | rts 64 | 65 | m0_timera1: 66 | move.l m0_pal(pc),a0 67 | lea $ffff8240.w,a1 68 | move #200-1,d0 69 | 70 | rept 23 71 | or.l d0,d0 72 | endr 73 | 74 | m0_spcbcl: 75 | movem.l (a0)+,d2-d7/a2-a6 ; 12+11*8 = 100 76 | movem.l d2-d7/a2-a3,(a1) ; 8+8*8 = 72 77 | movem.l (a0)+,d2-d7/a2-a3 ; 12+8*8 = 76 78 | movem.l d2-d7/a2-a3,(a1) ; 8+8*8 = 72 79 | movem.l (a0)+,d2-d7/a2-a3 ; 12+8*8 = 76 80 | movem.l d2-d7/a2-a3,(a1) ; 8+8*8 = 72 81 | movem.l a4-a6,(a1) ; 8+3*8 = 32 = 500 (54 colors) 82 | 83 | dbra d0,m0_spcbcl 84 | 85 | rts 86 | -------------------------------------------------------------------------------- /mode1.s: -------------------------------------------------------------------------------- 1 | ;--------------------------------------------------------------------- 2 | ; Multipalette routine. 3 | ; by Zerkman / Sector One 4 | ; mode 1: 320x200, CPU based, displays 48 colors per scanline 5 | ; with uniform repartition of color change positions. 6 | ;--------------------------------------------------------------------- 7 | 8 | ; Copyright (c) 2012-2025 Francois Galea 9 | ; This program is free software. It comes without any warranty, to 10 | ; the extent permitted by applicable law. You can redistribute it 11 | ; and/or modify it under the terms of the Do What The Fuck You Want 12 | ; To Public License, Version 2, as published by Sam Hocevar. See 13 | ; the COPYING file or http://www.wtfpl.net/ for more details. 14 | 15 | ; Plugin header. 16 | m1_begin: 17 | dc.w 320 ; width 18 | dc.w 200 ; height 19 | dc.w 48 ; colors per scanline 20 | dc.w 46 ; stored colors per scanline 21 | dc.w 160 ; physical screen line size in bytes 22 | dc.w 189 ; timer A data 23 | dc.w 4 ; flags: 4 calls to nextshift 24 | m1_pal: dc.l 0 ; palette address 25 | bra.w m1_init 26 | bra.w m1_palette_unpack 27 | m1_tab: bra.w m1_timera1 28 | 29 | 30 | ; Palette unpacking. 31 | ; a0: destination unpacked palette 32 | ; a5: get_color function 33 | ; d5-d7/a4-a6 : reserved for get_color function 34 | m1_palette_unpack: 35 | move #200-1,d2 ; line counter 36 | m1_pu_newline: 37 | clr (a0)+ ; set color 0 to black 38 | moveq #46,d1 ; 48 colors per line, -2 always black 39 | m1_pu_newcol: 40 | cmp #15,d1 41 | bne.s m1_pu_no48 42 | clr (a0)+ ; set color 32 to black 43 | subq #1,d1 ; next color 44 | 45 | m1_pu_no48: 46 | jsr (a5) 47 | move d0,(a0)+ 48 | dbra d1,m1_pu_newcol 49 | dbra d2,m1_pu_newline 50 | rts 51 | 52 | 53 | ; Init routine. 54 | m1_init: 55 | move.b #2,$ffff820a.w 56 | clr.b $ffff8260.w 57 | 58 | rts 59 | 60 | m1_timera1: 61 | move.l m1_pal(pc),a0 62 | lea $ffff8240.w,a1 63 | move #200-1,d0 64 | 65 | rept 29 66 | or.l d0,d0 67 | endr 68 | 69 | m1_spcbcl: 70 | move.l a1,a2 ; 4 71 | move.l a1,a3 ; 4 72 | move.l a1,a4 ; 4 73 | rept 8 74 | move.l (a0)+,(a2)+ ; 20*8 = 160 75 | endr 76 | rept 8 77 | move.l (a0)+,(a3)+ ; 20*8 = 160 78 | endr 79 | rept 8 80 | move.l (a0)+,(a4)+ ; 20*8 = 160 81 | endr 82 | rept 2 83 | nop ; 2*2 = 4 84 | endr 85 | 86 | dbra d0,m1_spcbcl 87 | 88 | rts 89 | -------------------------------------------------------------------------------- /mode2.s: -------------------------------------------------------------------------------- 1 | ;--------------------------------------------------------------------- 2 | ; Multipalette routine. 3 | ; by Zerkman / Sector One 4 | ; mode 2: 320x200, blitter based, displays 56 colors per scanline 5 | ; with uniform repartition of color change positions. 6 | ;--------------------------------------------------------------------- 7 | 8 | ; Copyright (c) 2012-2025 Francois Galea 9 | ; This program is free software. It comes without any warranty, to 10 | ; the extent permitted by applicable law. You can redistribute it 11 | ; and/or modify it under the terms of the Do What The Fuck You Want 12 | ; To Public License, Version 2, as published by Sam Hocevar. See 13 | ; the COPYING file or http://www.wtfpl.net/ for more details. 14 | 15 | ; Plugin header. 16 | m2_begin: 17 | dc.w 320 ; width 18 | dc.w 200 ; height 19 | dc.w 64 ; colors per scanline 20 | dc.w 54 ; stored colors per scanline 21 | dc.w 160 ; physical screen line size in bytes 22 | dc.w 189 ; timer A data 23 | dc.w flag_steonly+4 ; STe only + 4 calls to nextshift 24 | m2_pal: dc.l 0 ; palette address 25 | bra.w m2_init 26 | bra.w m2_palette_unpack 27 | m2_tab: bra.w m2_timera1 28 | 29 | 30 | ; Palette unpacking. 31 | ; a0: destination unpacked palette 32 | ; a5: get_color function 33 | ; d5-d7/a4-a6 : reserved for get_color function 34 | m2_palette_unpack: 35 | move #200-1,d2 ; line counter 36 | m2_pu_newline: 37 | clr (a0)+ ; set color 0 to black 38 | moveq #54,d1 ; 56 colors per line, -2 always black 39 | m2_pu_newcol: 40 | cmp #7,d1 41 | bne.s m2_pu_no48 42 | clr (a0)+ ; set color 48 to black 43 | subq #1,d1 ; next color 44 | 45 | m2_pu_no48: 46 | jsr (a5) 47 | move d0,(a0)+ 48 | dbra d1,m2_pu_newcol 49 | 50 | clr.l (a0)+ 51 | clr.l (a0)+ 52 | clr.l (a0)+ 53 | clr.l (a0)+ 54 | dbra d2,m2_pu_newline 55 | rts 56 | 57 | 58 | ; Init routine. 59 | m2_init: 60 | move.b #2,$ffff820a.w 61 | clr.b $ffff8260.w 62 | 63 | move #2,$ffff8a20.w ; source X increment 64 | move #2,$ffff8a22.w ; source Y increment 65 | move #-1,$ffff8a28.w ; Endmask 1 (first write of a line) 66 | move #-1,$ffff8a2a.w ; Endmask 2 (all other writes of a line) 67 | move #-1,$ffff8a2c.w ; Endmask 3 (last write of a line) 68 | move #2,$ffff8a2e.w ; destination X increment 69 | move #-30,$ffff8a30.w ; destination Y increment 70 | move #16,$ffff8a36.w ; words per line in bit-block 71 | move.b #2,$ffff8a3a.w ; halftone operation (2=source) 72 | move.b #3,$ffff8a3b.w ; logical operation (3=source) 73 | move.b #0,$ffff8a3d.w ; skew 74 | 75 | move mch(pc),d0 76 | cmp #2,d0 ; Mega STE? 77 | bne.s m2iste 78 | move #$4e71,m2_stetm ; replace one or.l with nop to adapt to blitter timing 79 | m2iste: 80 | rts 81 | 82 | m2_timera1: 83 | move.l m2_pal(pc),$ffff8a24.w ; source address register 84 | move.l #$ffff8240,$ffff8a32.w ; destination address register 85 | m2_stetm: 86 | rept 27 87 | or.l d0,d0 88 | endr 89 | nop 90 | move #200*4,$ffff8a38.w ; y count (HBL=62, LineCycles=428) 91 | move.b #$c0,$ffff8a3c.w ; line number register = busy, hog bus 92 | rts 93 | -------------------------------------------------------------------------------- /mode3.s: -------------------------------------------------------------------------------- 1 | ;--------------------------------------------------------------------- 2 | ; Multipalette routine. 3 | ; by Zerkman / Sector One 4 | ; mode 3: 416x276, CPU based, displays 48+6 colors per scanline 5 | ; with overscan and non-uniform repartition of color changes. 6 | ;--------------------------------------------------------------------- 7 | 8 | ; Copyright (c) 2012-2025 Francois Galea 9 | ; This program is free software. It comes without any warranty, to 10 | ; the extent permitted by applicable law. You can redistribute it 11 | ; and/or modify it under the terms of the Do What The Fuck You Want 12 | ; To Public License, Version 2, as published by Sam Hocevar. See 13 | ; the COPYING file or http://www.wtfpl.net/ for more details. 14 | 15 | nops macro 1 16 | rept \1/2 17 | or.l d0,d0 18 | endr 19 | rept \1%2 20 | nop 21 | endr 22 | endm 23 | 24 | ; common code in all scanlines 25 | scanline_base macro 26 | move a3,(a3) ; LineCycles = 0 -> 8 27 | move.b d0,(a3) ; LineCycles = 8 -> 16 28 | movem.l (a0)+,d2-d7/a4-a5 ; 19 29 | movem.l d2-d7/a4-a5,(a1) ; 18 30 | movem.l (a0)+,d0-d7/a4-a6 ; 25 31 | movem.l d0-d7,(a1) ; 18 32 | movem.l a4-a6,(a1) ; 8 33 | moveq #0,d0 ; LineCycles = 368 -> 372 34 | move.b d0,(a2) ; LineCycles = 372 -> 380 35 | move a2,(a2) ; LineCycles = 380 -> 388 36 | movem.l (a0)+,d1-d5 ; 13 37 | move a3,(a3) ; LineCycles = 440 -> 448 38 | nop 39 | move.b d0,(a3) ; LineCycles = 452 -> 460 40 | endm 41 | 42 | ; normal scanline 48 colours 43 | scanline_normal macro 44 | scanline_base 45 | movem.l d1-d5,(a7) ; 12 46 | nop 47 | endm 48 | 49 | ; special scanline (with vertical border opening), 44 colours only 50 | scanline_special macro 51 | scanline_base 52 | move.b d0,(a2) 53 | movem.l d1-d3,(a7) ; 8 54 | move a2,(a2) 55 | nop 56 | endm 57 | 58 | ; Plugin header. 59 | m3_begin: 60 | dc.w 416 ; width 61 | dc.w 276 ; height 62 | dc.w 48 ; colors per scanline 63 | dc.w 48 ; stored colors per scanline 64 | dc.w 230 ; physical screen line size in bytes 65 | dc.w 99 ; timer A data 66 | dc.w 1 ; flags: 1 call to nextshift 67 | m3_pal: dc.l 0 ; palette address 68 | bra.w m3_init 69 | bra.w m3_palette_unpack 70 | m3_tab: bra.w m3_timera1 71 | 72 | 73 | ; Palette unpacking. 74 | ; a0: destination unpacked palette 75 | ; a5: get_color function 76 | ; d5-d7/a4-a6 : reserved for get_color function 77 | m3_palette_unpack: 78 | move #276-1,d2 ; line counter 79 | m3_pu_newline: 80 | moveq #47,d1 ; 48 colors per line 81 | m3_pu_newcol: 82 | jsr (a5) 83 | move d0,(a0)+ 84 | dbra d1,m3_pu_newcol 85 | dbra d2,m3_pu_newline 86 | rts 87 | 88 | ; Init routine. 89 | m3_init: 90 | move.b #2,$ffff820a.w 91 | clr.b $ffff8260.w 92 | 93 | rts 94 | 95 | m3_timera1: 96 | move.l a7,usp 97 | move.l m3_pal(pc),a0 98 | lea $ffff8240.w,a1 99 | lea $ffff820a.w,a2 100 | lea $ffff8260.w,a3 101 | lea $ffff824c.w,a7 102 | moveq #0,d0 103 | nops 44 104 | movem.l (a0)+,d1-d5 105 | movem.l d1-d5,(a7) 106 | 107 | ; Generic top border opening 108 | move.b d0,(a2) ; LineCycles=488 109 | nops 2 110 | move a2,(a2) ; LineCycles=504 - L16:16 111 | 112 | 113 | rept 228 114 | scanline_normal 115 | endr 116 | 117 | scanline_special 118 | 119 | rept 44 120 | scanline_normal 121 | endr 122 | 123 | scanline_special 124 | 125 | scanline_normal 126 | scanline_normal 127 | 128 | move.l usp,a7 129 | move #$2300,sr 130 | rts 131 | -------------------------------------------------------------------------------- /mpp2bmp.c: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------- 2 | MPP to BMP file converter. 3 | by Zerkman / Sector One 4 | ------------------------------------------------------------------- */ 5 | 6 | /* Copyright (c) 2012-2025 Francois Galea 7 | * This program is free software. It comes without any warranty, to 8 | * the extent permitted by applicable law. You can redistribute it 9 | * and/or modify it under the terms of the Do What The Fuck You Want 10 | * To Public License, Version 2, as published by Sam Hocevar. See 11 | * the COPYING file or http://www.wtfpl.net/ for more details. */ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "pixbuf.h" 18 | 19 | typedef struct { 20 | int id; 21 | int ncolors; 22 | int nfixed; 23 | int border0; 24 | int x0; 25 | int (*xinc)(int); 26 | int xdelta; 27 | int width; 28 | int height; 29 | } Mode; 30 | 31 | static int xinc0(int c) { return ((c==15)?88:((c==31)?12:((c==37)?100:4))); } 32 | static int xinc1(int c) { return (((c)&1)?16:4); } 33 | static int xinc2(int c) { return 8; } 34 | static int xinc3(int c) { return ((c==15)?112:((c==31)?12:((c==37)?100:4))); } 35 | 36 | static const Mode modes[4] = { 37 | { 0, 54, 0, 1, 33, xinc0, 148, 320, 200 }, 38 | { 1, 48, 0, 1, 9, xinc1, 160, 320, 200 }, 39 | { 2, 56, 0, 1, 5, xinc2, 128, 320, 200 }, 40 | { 3, 54, 6, 0, 69, xinc3, 160, 416, 276 }, 41 | }; 42 | 43 | static int read32(const void *ptr) { 44 | const unsigned char *p = ptr; 45 | return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3]; 46 | } 47 | 48 | PixBuf *decode_mpp(const Mode *mode, int ste, int extra, FILE *in) { 49 | int bits = 3 * (3 + ste + extra); 50 | int palette_size = (mode->ncolors-mode->nfixed)*mode->height; 51 | int packed_palette_size = ((bits*(mode->ncolors-mode->nfixed-mode->border0*2)*mode->height+15)/8)&-2; 52 | int extra_palette_bytes = palette_size*sizeof(Pixel) - packed_palette_size; 53 | int image_size = mode->width/2*mode->height; 54 | Pixel *pal = malloc(palette_size*sizeof(Pixel)); 55 | unsigned char *img = malloc(image_size); 56 | unsigned char *p = ((unsigned char*)pal)+extra_palette_bytes; 57 | int bitbuf = 0, bbnbits = 0; 58 | int i; 59 | 60 | fread(p, packed_palette_size, 1, in); 61 | fread(img, image_size, 1, in); 62 | 63 | for (i = 0; i < palette_size; i++) { 64 | int x = i % mode->ncolors; 65 | if (mode->border0 && (x == 0 || x == ((mode->ncolors-1) & -16))) { 66 | pal[i].rgb = 0; 67 | continue; 68 | } 69 | while (bbnbits < bits) { 70 | bitbuf = (bitbuf << 8) | *p++; 71 | bbnbits += 8; 72 | } 73 | bbnbits -= bits; 74 | unsigned short c = (bitbuf >> bbnbits) & ((1<>6); 80 | p.cp.g = (c>>3)&7; 81 | p.cp.b = c&7; 82 | break; 83 | case 12: 84 | case 15: 85 | p.cp.r = ((c>>7)&0xe) | ((c>>11)&1); 86 | p.cp.g = ((c>>3)&0xe) | ((c>>7)&1); 87 | p.cp.b = ((c<<1)&0xe) | ((c>>3)&1); 88 | if (bits == 15) { 89 | p.cp.r = (p.cp.r << 1) | ((c>>14)&1); 90 | p.cp.g = (p.cp.g << 1) | ((c>>13)&1); 91 | p.cp.b = (p.cp.b << 1) | ((c>>12)&1); 92 | } 93 | break; 94 | default: 95 | abort(); 96 | } 97 | p.cp.r <<= 8 - (bits/3); 98 | p.cp.g <<= 8 - (bits/3); 99 | p.cp.b <<= 8 - (bits/3); 100 | pal[i] = p; 101 | } 102 | 103 | PixBuf *pix = pixbuf_new(mode->width, mode->height); 104 | memset(pix->array, 0, mode->width * mode->height * sizeof(Pixel)); 105 | int k=0, l=0; 106 | int x, y; 107 | unsigned short b0=0, b1=0, b2=0, b3=0; 108 | Pixel palette[16]; 109 | memset(palette, 0, sizeof(Pixel)); 110 | for (y=0; yheight; ++y) { 111 | Pixel *ppal = pal + y * (mode->ncolors-mode->nfixed); 112 | int nextx = mode->x0; 113 | int nextc = 0; 114 | for (i=mode->nfixed; i<16; ++i) 115 | palette[i] = *ppal++; 116 | for (x=0; xwidth; ++x) { 117 | if (x==nextx) { 118 | palette[nextc&0xf] = *ppal++; 119 | nextx += mode->xinc(nextc); 120 | ++nextc; 121 | } 122 | if ((x&0xf) == 0) { 123 | b0 = (img[k+0]<<8) | (img[k+1]); 124 | b1 = (img[k+2]<<8) | (img[k+3]); 125 | b2 = (img[k+4]<<8) | (img[k+5]); 126 | b3 = (img[k+6]<<8) | (img[k+7]); 127 | k += 8; 128 | } 129 | i = ((b3>>12)&8) | ((b2>>13)&4) | ((b1>>14)&2) | ((b0>>15)&1); 130 | pix->array[l++] = palette[i]; 131 | b0 <<= 1; 132 | b1 <<= 1; 133 | b2 <<= 1; 134 | b3 <<= 1; 135 | } 136 | } 137 | free(pal); 138 | free(img); 139 | return pix; 140 | } 141 | 142 | int main(int argc, char **argv) 143 | { 144 | if (argc < 2) { 145 | fprintf(stderr, "usage: %s file.mpp [output.bmp]\n", argv[0]); 146 | return 1; 147 | } 148 | const char *filename = argv[1]; 149 | unsigned char header[12]; 150 | 151 | FILE *in = fopen(filename, "rb"); 152 | if (!in) { 153 | perror(filename); 154 | return 1; 155 | } 156 | fread(header, 12, 1, in); 157 | const Mode *mode = &modes[header[3]]; 158 | int doubl = (header[4]&4) >> 2; 159 | int extra = (header[4]&2) >> 1; 160 | int ste = header[4]&1; 161 | int skip = read32(header+8); 162 | 163 | if (skip) 164 | fseek(in, skip, SEEK_CUR); 165 | PixBuf *pix = decode_mpp(mode, ste, extra, in); 166 | if (doubl) { 167 | PixBuf *pix2 = decode_mpp(mode, ste, extra, in); 168 | int k; 169 | for (k = 0; k < mode->width*mode->height; ++k) { 170 | Pixel a = pix->array[k], b = pix2->array[k]; 171 | a.cp.r = ((int)a.cp.r + b.cp.r)/2; 172 | a.cp.g = ((int)a.cp.g + b.cp.g)/2; 173 | a.cp.b = ((int)a.cp.b + b.cp.b)/2; 174 | pix->array[k] = a; 175 | } 176 | pixbuf_delete(pix2); 177 | } 178 | fclose(in); 179 | 180 | if (argc > 2) 181 | pixbuf_export_bmp(pix, argv[2]); 182 | else { 183 | char buf[256]; 184 | char *dot = strrchr(argv[1], '.'); 185 | int len; 186 | len = dot ? (dot-argv[1]) : strlen(argv[1]); 187 | strncpy(buf, argv[1], len); 188 | strcpy(buf+len, ".bmp"); 189 | pixbuf_export_bmp(pix, buf); 190 | } 191 | pixbuf_delete(pix); 192 | 193 | return 0; 194 | } 195 | -------------------------------------------------------------------------------- /mppdec.s: -------------------------------------------------------------------------------- 1 | ;--------------------------------------------------------------------- 2 | ; Decoder and display code for Plugin-based multipalette picture (MPP) format. 3 | ; by Zerkman / Sector One 4 | ;--------------------------------------------------------------------- 5 | 6 | ; Copyright (c) 2012-2025 Francois Galea 7 | ; This program is free software. It comes without any warranty, to 8 | ; the extent permitted by applicable law. You can redistribute it 9 | ; and/or modify it under the terms of the Do What The Fuck You Want 10 | ; To Public License, Version 2, as published by Sam Hocevar. See 11 | ; the COPYING file or http://www.wtfpl.net/ for more details. 12 | 13 | ; plugin structure entry offsets 14 | plug_width equ 0 15 | plug_height equ 2 16 | plug_colors_per_scanline equ 4 17 | plug_stored_colors_per_scanline equ 6 18 | plug_line_size equ 8 19 | plug_timera_data equ 10 20 | plug_flags equ 12 21 | plug_pal_adr equ 14 22 | plug_init equ 18 23 | plug_palette_unpack equ 22 24 | plug_timera equ 26 25 | 26 | flag_nshiftmask equ $7 ; number of calls to nextshift at init 27 | flag_steonly equ $8 ; STE only mode 28 | 29 | ; MPP viewer structure 30 | rsreset 31 | plug rs.l 1 32 | picp0 rs.l 1 33 | picp1 rs.l 1 34 | palp0 rs.l 1 35 | palp1 rs.l 1 36 | dbifg rs.b 1 ; double image flag 37 | dbpfg rs.b 1 ; double palette flag 38 | mppstrsize rs.b 1 39 | 40 | ; Initialize MPP viewer 41 | mpp_init: 42 | bsr get_machine_type 43 | move d0,mch 44 | rts 45 | 46 | ; Decode a MPP file 47 | ; a0: (i) file address 48 | ; a1: (i) address of MPP stucture and necessary buffers following 49 | mpp_decode: 50 | move.l a0,a3 ; store header address 51 | move.l a0,a4 ; source stream pointer 52 | addq.l #8,a4 53 | add.l (a4)+,a4 ; extra header info, to be skipped 54 | 55 | moveq #0,d0 56 | move.b 3(a3),d0 ; mode 57 | add d0,d0 58 | lea plugins(pc),a6 59 | add (a6,d0.w),a6 ; plugin header address 60 | move.l a6,plug(a1) 61 | 62 | move plug_line_size(a6),d1 63 | mulu plug_height(a6),d1 ; screen size 64 | move.l a1,d0 65 | add.l #mppstrsize+254,d0 66 | clr.b d0 ; first screen address 67 | move.l d0,picp0(a1) 68 | add.l d1,d0 ; end of screen address 69 | btst #2,4(a3) ; double image ? 70 | beq.s ndbi 71 | add.l #254,d0 72 | clr.b d0 ; second screen address 73 | move.l d0,picp1(a1) 74 | add.l d1,d0 ; end of screen 75 | ndbi: move.l d0,palp0(a1) ; first palette 76 | move plug_colors_per_scanline(a6),d1 77 | mulu plug_height(a6),d1 78 | add.l d1,d1 79 | add.l d1,d0 80 | move.l d0,palp1(a1) ; second palette address even if not used 81 | 82 | move plug_flags(a6),d0 83 | and #flag_steonly,d0 84 | beq.s mlvf0 85 | move mch(pc),d0 ; test if STe only and machine == ST 86 | bne.s mlvf0 87 | moveq #-1,d0 88 | rts 89 | mlvf0: 90 | 91 | move.b 4(a3),d0 ; flags 92 | moveq #9,d5 ; size of palette entry in bits 93 | btst #0,d0 ; STE palette ? 94 | beq.s noste 95 | addq #3,d5 96 | noste: btst #1,d0 ; extra palette bit ? 97 | sne dbpfg(a1) ; dbpfg 98 | beq.s noxtra 99 | addq #3,d5 100 | noxtra: 101 | 102 | move.l palp0(a1),a0 ; palp0 103 | bsr dcpal 104 | 105 | move.l picp0(a1),a0 ; picp0 106 | bsr dcimg 107 | 108 | btst #2,4(a3) ; double image ? 109 | sne dbifg(a1) ; dbifg 110 | beq.s nord2n 111 | 112 | ; Read second image 113 | st dbpfg(a1) ; dbpfg 114 | move.l palp1(a1),a0 ; palp1 115 | bsr dcpal 116 | move.l picp1(a1),a0 ; picp1 117 | bsr dcimg 118 | 119 | nord2n: 120 | move mch(pc),d0 ; test if machine == ST 121 | bne.s mlvnst 122 | btst #2,4(a3) ; double image ? 123 | bne noxtr2 124 | cmp #9,d5 ; 12-bit mode on ST ? 125 | beq noxtr2 126 | 127 | ; Handle 12-bit (STe) mode on the ST. 128 | st dbpfg(a1) ; dbpfg 129 | move.l palp0(a1),a0 ; palp0, source palette 130 | move plug_colors_per_scanline(a6),d3 131 | subq #1,d3 132 | 133 | move.l palp1(a1),a2 ; palp1, destination palette 134 | 135 | move plug_height(a6),d1 136 | subq #1,d1 ; height counter 137 | seblln: move d3,d2 ; line color counter 138 | sebl0: move (a0),d0 139 | move d0,(a2) 140 | 141 | lsl.w #4,d0 142 | moveq #3-1,d5 ; bit counter 143 | sebl1: rol.w #4,d0 144 | moveq #$f,d6 145 | and d0,d6 ; isolate component 146 | eor d6,d0 ; clear component 147 | or.b etoe1(pc,d6),d0 ; component + 1 148 | dbra d5,sebl1 149 | 150 | btst #0,d1 ; even or odd line ? 151 | beq.s sebl3 152 | move d0,(a0) 153 | bra.s sebl4 154 | 155 | sebl3: move d0,(a2) 156 | sebl4: addq.l #2,a0 157 | addq.l #2,a2 158 | dbra d2,sebl0 159 | dbra d1,seblln 160 | 161 | bra.s noxtr2 162 | 163 | 164 | mlvnst: btst #1,4(a3) ; extra bit ? 165 | beq.s noxtr2 166 | 167 | ; Handle extra bit palette 168 | move.l palp0(a1),a0 ; palp0 169 | move plug_colors_per_scanline(a6),d3 170 | subq #1,d3 171 | 172 | move.l palp1(a1),a2 ; palp1, destination palette 173 | 174 | move plug_height(a6),d1 175 | subq #1,d1 ; height counter 176 | exblln: move d3,d2 ; line color counter 177 | exbl0: move (a0),d0 178 | move d0,(a2) 179 | move d0,d4 180 | rol #4,d4 181 | and #7,d4 ; isolate the 3 extra bits 182 | moveq #3-1,d5 ; bit counter 183 | exbl1: lsr #1,d4 ; test bit 184 | bcc.s exbl2 185 | moveq #$f,d6 186 | and d0,d6 ; isolate last component 187 | and #$fff0,d0 188 | or.b etoe1(pc,d6),d0 ; component + 1 189 | exbl2: ror #4,d0 ; switch to next component 190 | dbra d5,exbl1 191 | ror #4,d0 192 | btst #0,d1 ; even or odd line ? 193 | beq.s exbl3 194 | move d0,(a0) 195 | bra.s exbl4 196 | 197 | ; table to add 1 to STE color components 198 | etoe1: dc.b 8, 9, 10, 11, 12, 13, 14, 15, 1, 2, 3, 4, 5, 6, 7, 15 199 | 200 | exbl3: move d0,(a2) 201 | exbl4: addq.l #2,a0 202 | addq.l #2,a2 203 | dbra d2,exbl0 204 | dbra d1,exblln 205 | 206 | noxtr2: 207 | moveq #0,d0 208 | rts 209 | 210 | 211 | ; Generic function to get a color from a stream with variable number of bits. 212 | ; d5: number of bits to read 213 | ; d6: available bits in buffer 214 | ; d7: bit buffer 215 | ; a4: stream pointer 216 | ; d0: (o) read value 217 | get_color: 218 | sub d5,d6 ; enough bits in bit buffer ? 219 | bpl.s gcl_getbits 220 | 221 | swap d7 222 | move (a4)+,d7 223 | add #16,d6 224 | 225 | gcl_getbits: 226 | move.l d7,d0 227 | lsr.l d6,d0 228 | rts 229 | 230 | 231 | ; Decode palette from file 232 | ; d5: (io) number of bits per palette entry 233 | ; a0: (io) Pointer to destination palette block 234 | ; a3: (io) Image file header address 235 | ; a4: (io) current position in file stream 236 | ; a6: (io) Image plugin address 237 | dcpal: 238 | ; Unpack palette 239 | move.l a0,a2 ; save destination block address 240 | moveq #0,d7 ; bit buffer 241 | moveq #0,d6 ; bit counter 242 | lea get_color(pc),a5; color 243 | jsr plug_palette_unpack(a6) 244 | 245 | btst #0,4(a3) ; ste palette ? 246 | bne.s nostp 247 | 248 | ; Convert 9-bit palette entries to ST format 249 | move.l a0,d3 ; end of decoded palette 250 | sub.l a2,d3 ; number of palette entries*2 251 | lsr.l #1,d3 ; number of palette entries 252 | subq #1,d3 ; palette entry counter 253 | stplp: move (a2),d0 254 | move d0,d1 255 | lsl #2,d1 256 | and #$700,d1 ; red component 257 | move d0,d2 258 | lsl #1,d2 259 | and #$070,d2 ; green component 260 | or d2,d1 261 | and #$007,d0 ; blue component 262 | or d0,d1 263 | move d1,(a2)+ 264 | dbra d3,stplp 265 | nostp: 266 | rts 267 | 268 | ; Decode image data 269 | ; a0: (io) Pointer to destination image block 270 | ; a3: (io) Image file header address 271 | ; a4: (io) current position in file stream 272 | ; a6: (io) Image plugin address 273 | dcimg: 274 | move plug_line_size(a6),d4 275 | move plug_width(a6),d3 276 | lsr #1,d3 ; source width in bytes 277 | sub d3,d4 ; line offset 278 | lsr #1,d3 279 | subq #1,d3 ; words per line counter 280 | move plug_height(a6),d1 281 | subq #1,d1 282 | dcil1: move d3,d0 283 | dcil0: move (a4)+,(a0)+ 284 | dbra d0,dcil0 285 | add d4,a0 286 | dbra d1,dcil1 287 | 288 | rts 289 | 290 | ; Setup image for display 291 | ; a1: (io) address of MPP stucture 292 | mpp_setup_img: 293 | lea mppstr(pc),a0 294 | move.l a1,(a0) 295 | 296 | move.l plug(a1),a0 297 | move.l palp0(a1),plug_pal_adr(a0) 298 | lea tad+2(pc),a2 299 | move #$c2,(a2) 300 | 301 | jsr plug_init(a0) 302 | 303 | lea mpp_hbl(pc),a0 304 | move.l a0,$68.w 305 | 306 | lea mpp_vbl(pc),a0 ; Launch VBL 307 | move.l a0,$70.w ; 308 | 309 | lea mpp_timer_a_cfg(pc),a0 310 | move.l a0,$134.w 311 | move.b #$20,$fffffa07.w 312 | clr.b $fffffa09.w 313 | move.b #$20,$fffffa13.w 314 | clr.b $fffffa15.w 315 | 316 | rts 317 | 318 | 319 | mpp_vbl: 320 | clr.b $fffffa19.w 321 | tad: move.b #$c2,$fffffa1f.w 322 | move.b #$4,$fffffa19.w 323 | 324 | clr.l $ffff8240.w 325 | clr.l $ffff8244.w 326 | clr.l $ffff8248.w 327 | clr.l $ffff824c.w 328 | clr.l $ffff8250.w 329 | clr.l $ffff8254.w 330 | clr.l $ffff8258.w 331 | clr.l $ffff825c.w 332 | 333 | mpp_hbl: 334 | rte 335 | 336 | mpp_timer_a_cfg: 337 | clr.b $fffffa19.w 338 | bclr.b #5,$fffffa0f.w 339 | move #$2300,sr 340 | movem.l d2-d5/a2,-(sp) 341 | 342 | moveq #0,d5 343 | moveq #12,d3 344 | moveq #4,d2 345 | stop #$2100 346 | tacglp: 347 | stop #$2100 348 | moveq #0,d4 349 | move.b $ffff8209.w,d4 350 | sub.b d3,d4 351 | subq #4,d4 352 | neg d4 353 | add.b #$a0,d3 354 | lsr #2,d5 355 | lsl #7,d4 356 | or d4,d5 357 | dbra d2,tacglp 358 | 359 | lea snval(pc),a2 360 | move d5,(a2) 361 | 362 | lea mpp_timer_a(pc),a2 363 | move.l a2,$134.w 364 | move.l mppstr(pc),a2 365 | move.l plug(a2),a2 366 | move plug_timera_data(a2),d2 367 | moveq #flag_nshiftmask,d4 368 | and plug_flags(a2),d4 ; number of calls to nextshift 369 | lea tad+2(pc),a2 370 | move d2,(a2) 371 | 372 | subq #1,d4 373 | tacnslp: 374 | bsr mpp_nextshift 375 | dbra d4,tacnslp 376 | movem.l (sp)+,d2-d5/a2 377 | 378 | rte 379 | 380 | mpp_nextshift: 381 | lea snval(pc),a2 382 | move (a2),d2 383 | moveq #$3f,d3 384 | and d2,d3 385 | lsl #4,d3 386 | lsr #6,d2 387 | or d3,d2 388 | move d2,(a2) 389 | 390 | rts 391 | 392 | mpp_timer_a: 393 | clr.b $fffffa19.w 394 | bclr.b #5,$fffffa0f.w 395 | movem.l d0-a6,-(sp) 396 | move.l mppstr(pc),a0 397 | move.l plug(a0),a0 398 | stop #$2100 399 | stop #$2100 400 | move #$2700,sr 401 | moveq #3,d0 402 | and snval(pc),d0 403 | add d0,d0 404 | lsr d0,d0 405 | 406 | jsr plug_timera(a0) 407 | bsr next_pic 408 | bsr mpp_nextshift 409 | 410 | movem.l (sp)+,d0-a6 411 | rte 412 | 413 | next_pic: 414 | lea flick(pc),a0 415 | move (a0),d2 416 | bchg #2,d2 417 | move d2,(a0) 418 | move d2,d3 419 | move.l mppstr(pc),a0 420 | and.b dbifg(a0),d3 421 | move.l picp0(a0,d3.w),d0 ; picp0 or picp1 422 | and.b dbpfg(a0),d2 423 | move.l palp0(a0,d2.w),d1 ; palp0 or palp1 424 | 425 | move.l d0,d2 426 | lsr #8,d0 427 | move.l d0,$ffff8200.w 428 | move mch(pc),d0 429 | beq.s npnste 430 | move.b d2,$ffff820d.w 431 | npnste: 432 | move.l plug(a0),a0 433 | move.l d1,plug_pal_adr(a0) 434 | rts 435 | 436 | ; Retrieve the machine type. 437 | ; d0: (o) 0:ST, 1:STe, 2:MSTe, -1:unsupported (Falcon/TT/...) 438 | get_machine_type: 439 | move.l #'_MCH',d6 440 | bsr get_cookie 441 | 442 | cmp.l #-1,d0 ; no cookie jar or no cookie found 443 | beq.s gmtst 444 | 445 | swap d0 446 | tst d0 ; ST ? 447 | beq.s gmtst 448 | cmp #2,d0 ; machine type 449 | bpl.s gmtuns ; TT and Falcon are unsupported 450 | btst #20,d0 ; MSte ? 451 | beq.s gmtste 452 | moveq #2,d0 ; MSTe 453 | rts 454 | gmtste: moveq #1,d0 ; STe 455 | rts 456 | gmtst: moveq #0,d0 ; ST 457 | rts 458 | gmtuns: moveq #-1,d0 ; unsupported 459 | rts 460 | 461 | get_cookie: 462 | move.l $5a0.w,d0 ; _p_cookies 463 | beq.s gcknf 464 | move.l d0,a0 465 | gcknx: move.l (a0)+,d0 ; cookie name 466 | beq.s gcknf 467 | cmp.l d6,d0 468 | beq.s gckf 469 | addq.l #4,a0 470 | bra.s gcknx 471 | gckf: move.l (a0)+,d0 ; cookie value 472 | rts 473 | gcknf: moveq #-1,d0 474 | rts 475 | 476 | 477 | plugins: 478 | dc.w mode0-plugins, mode1-plugins, mode2-plugins, mode3-plugins 479 | 480 | mode0: include "mode0.s" 481 | mode1: include "mode1.s" 482 | mode2: include "mode2.s" 483 | mode3: include "mode3.s" 484 | 485 | mch: ds.w 1 ; machine type 486 | flick: ds.w 1 ; flick flag 487 | mppstr: ds.l 1 ; current MPP stucture 488 | snval: ds.w 1 ; video synclock value 489 | -------------------------------------------------------------------------------- /mppview.s: -------------------------------------------------------------------------------- 1 | ;--------------------------------------------------------------------- 2 | ; Plugin-based multipalette picture viewer. 3 | ; by Zerkman / Sector One 4 | ;--------------------------------------------------------------------- 5 | 6 | ; Copyright (c) 2012-2025 Francois Galea 7 | ; This program is free software. It comes without any warranty, to 8 | ; the extent permitted by applicable law. You can redistribute it 9 | ; and/or modify it under the terms of the Do What The Fuck You Want 10 | ; To Public License, Version 2, as published by Sam Hocevar. See 11 | ; the COPYING file or http://www.wtfpl.net/ for more details. 12 | 13 | dta_reserved equ 0 14 | dta_attrib equ 21 15 | dta_time equ 22 16 | dta_date equ 24 17 | dta_length equ 26 18 | dta_fname equ 30 19 | 20 | fa_rdonly equ $01 21 | fa_hidden equ $02 22 | fa_system equ $04 23 | fa_volume equ $08 24 | fa_dir equ $10 25 | fa_archive equ $20 26 | 27 | text 28 | 29 | pea mpp_init(pc) 30 | move #38,-(sp) ; Supexec 31 | trap #14 32 | addq.l #6,sp 33 | move mch(pc),d0 ; 0:ST, 1:STe, 2:MSTe, -1:unsupported 34 | bmi unsup_machine_error 35 | 36 | move #47,-(sp) ; Fgetdta 37 | trap #1 38 | addq.l #2,sp 39 | move.l d0,a6 ; dta 40 | 41 | move.l a6,a0 42 | moveq #0,d0 43 | move.b (a0)+,d0 ; command line length 44 | beq.s scandir ; if empty command line, scan the current dir 45 | 46 | clr.b (a0,d0.w) ; zero byte at end of command line 47 | bsr mpploadnview 48 | bra bye 49 | 50 | scandir: 51 | move #fa_rdonly|fa_archive,-(sp) 52 | pea fspec(pc) 53 | move #78,-(sp) ; Fsfirst 54 | trap #1 55 | addq.l #8,sp 56 | 57 | fileloop: 58 | tst d0 59 | bne bye 60 | 61 | lea dta_fname(a6),a0 62 | move.l a6,-(sp) 63 | bsr mpploadnview 64 | move.l (sp)+,a6 65 | 66 | move #79,-(sp) ; Fsnext 67 | trap #1 68 | addq.l #2,sp 69 | bra fileloop 70 | 71 | bye: 72 | clr -(sp) 73 | trap #1 74 | 75 | fspec: dc.b "*.MPP",0 76 | even 77 | 78 | ; Reads a MPP file and display it 79 | ; a0: (i) file name 80 | mpploadnview: 81 | move.l a0,a5 ; remember the file name 82 | clr -(sp) ; read mode 83 | move.l a0,-(sp) 84 | move #61,-(sp) ; Fopen 85 | trap #1 86 | addq.l #8,sp 87 | 88 | move d0,d6 ; file handle 89 | bmi read_file_error 90 | 91 | lea img,a3 92 | move.l a3,-(sp) 93 | pea imgbuf-img ; max read size 94 | move d6,-(sp) ; file handle 95 | move #63,-(sp) ; Fread 96 | trap #1 97 | lea 12(sp),sp 98 | 99 | move d6,-(sp) 100 | move #62,-(sp) ; Fclose 101 | trap #1 102 | addq.l #4,sp 103 | 104 | lea imgbuf,a1 105 | move.l a3,a0 106 | bsr mpp_decode 107 | bne cant_view_ste_warn 108 | 109 | ; Run main loop 110 | pea mainsup(pc) 111 | move #38,-(sp) ; Supexec 112 | trap #14 113 | addq.l #6,sp 114 | 115 | rts 116 | 117 | 118 | unsup_machine_error: 119 | lea unsup_error_txt(pc),a0 120 | bsr.s _cconws 121 | bra.s end 122 | 123 | cant_view_ste_warn: 124 | lea cant_view_ste_txt1(pc),a0 125 | bsr.s _cconws 126 | move.l a5,a0 127 | bsr.s _cconws 128 | lea cant_view_ste_txt2(pc),a0 129 | bsr.s _cconws 130 | bra.s presskey 131 | rts 132 | 133 | read_file_error: 134 | lea open_error_txt1(pc),a0 135 | bsr.s _cconws 136 | move.l a5,a0 137 | bsr.s _cconws 138 | lea open_error_txt2(pc),a0 139 | bsr.s _cconws 140 | 141 | end: 142 | bsr.s presskey 143 | clr -(sp) 144 | trap #1 145 | 146 | presskey: 147 | lea presskey_txt(pc),a0 148 | bsr.s _cconws 149 | 150 | move.w #7,-(sp) ; Crawcin 151 | trap #1 152 | addq.l #2,sp 153 | rts 154 | 155 | _cconws: 156 | move.l a0,-(sp) 157 | move #9,-(sp) ; Cconws 158 | trap #1 159 | addq.l #6,sp 160 | rts 161 | 162 | 163 | ; Main routine, supervisor mode 164 | mainsup: 165 | move.l $462.w,d0 ; _vbclock 166 | vs: cmp.l $462.w,d0 167 | beq.s vs 168 | 169 | move #$2700,sr 170 | 171 | ; Setup VBL, timers & co. 172 | lea -128(sp),sp 173 | move.l sp,a0 174 | move.l usp,a1 175 | move.l a1,(a0)+ 176 | cmp #2,mch ; MSte ? 177 | bne.s msnm 178 | move $ffff8e20.w,(a0)+ 179 | clr.b $ffff8e21.w 180 | msnm: move.l $ffff8200.w,(a0)+ 181 | move.b $ffff820a.w,(a0)+ 182 | move.b $ffff8260.w,(a0)+ 183 | move.l $68.w,(a0)+ 184 | move.l $70.w,(a0)+ 185 | move.l $134.w,(a0)+ 186 | move.b $fffffa07.w,(a0)+ 187 | move.b $fffffa09.w,(a0)+ 188 | move.b $fffffa13.w,(a0)+ 189 | move.b $fffffa15.w,(a0)+ 190 | move.b $fffffa17.w,(a0)+ 191 | addq.l #1,a0 192 | movem.l $ffff8240.w,d1-d7/a1 193 | movem.l d1-d7/a1,(a0) 194 | 195 | move.l sp,vecptr 196 | 197 | lea imgbuf,a1 198 | bsr mpp_setup_img 199 | 200 | move #$2300,sr 201 | 202 | ; Main loop that just waits for a press on space bar 203 | main_loop: 204 | move.b $fffffc02.w,d0 205 | cmp.b #$39,d0 206 | bne.s main_loop 207 | 208 | move #$2700,sr 209 | clr.b $fffffa19.w 210 | 211 | move.l vecptr,sp 212 | 213 | move.l sp,a0 214 | move.l (a0)+,a1 215 | move.l a1,usp 216 | cmp #2,mch ; MSte ? 217 | bne.s enm 218 | move (a0)+,$ffff8e20.w 219 | enm: move.l (a0)+,$ffff8200.w 220 | move.b (a0)+,$ffff820a.w 221 | move.b (a0)+,$ffff8260.w 222 | move.l (a0)+,$68.w 223 | move.l (a0)+,$70.w 224 | move.l (a0)+,$134.w 225 | move.b (a0)+,$fffffa07.w 226 | move.b (a0)+,$fffffa09.w 227 | move.b (a0)+,$fffffa13.w 228 | move.b (a0)+,$fffffa15.w 229 | move.b (a0)+,$fffffa17.w 230 | addq.l #1,a0 231 | movem.l (a0)+,d1-d7/a1 232 | movem.l d1-d7/a1,$ffff8240.w 233 | 234 | move #$2300,sr 235 | 236 | lea 128(sp),sp 237 | rts 238 | 239 | 240 | open_error_txt1: 241 | dc.b "Could not find file '",0 242 | open_error_txt2: 243 | dc.b "'.",$d,$a,0 244 | cant_view_ste_txt1: 245 | dc.b "The file '",0 246 | cant_view_ste_txt2: 247 | dc.b "' cannot be viewed (STe only).",$d,$a,0 248 | unsup_error_txt: 249 | dc.b "This program only works on ST, STe and Mega STe.",$d,$a,0 250 | presskey_txt: 251 | dc.b "Press any key.",$d,$a,0 252 | even 253 | 254 | include "mppdec.s" 255 | 256 | bss 257 | z: 258 | vecptr: ds.l 1 259 | img: ds.b 200000 260 | imgbuf: ds.b mppstrsize+2*(230*276+254)+2*(276*48*2) 261 | -------------------------------------------------------------------------------- /pixbuf.c: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------- 2 | Management of a basic pixel buffer with export to BMP file format. 3 | by Zerkman / Sector One 4 | ------------------------------------------------------------------- */ 5 | 6 | /* Copyright (c) 2012-2025 Francois Galea 7 | * This program is free software. It comes without any warranty, to 8 | * the extent permitted by applicable law. You can redistribute it 9 | * and/or modify it under the terms of the Do What The Fuck You Want 10 | * To Public License, Version 2, as published by Sam Hocevar. See 11 | * the COPYING file or http://www.wtfpl.net/ for more details. */ 12 | 13 | #include 14 | #include 15 | 16 | #include "pixbuf.h" 17 | 18 | void 19 | __mem_error(const void *ptr, const char *name, const char* file, int line) { 20 | if (!ptr) { 21 | fprintf(stderr, "%s:%d:allocation for pointer `%s' failed\n", 22 | file, line, name); 23 | exit(1); 24 | } 25 | } 26 | 27 | #define MEM_ERROR(ptr) __mem_error(ptr, #ptr, __FILE__, __LINE__) 28 | 29 | static void write16(void *ptr, uint16_t val) { 30 | char *p = (char *)ptr; 31 | p[0] = val; 32 | p[1] = val>>8; 33 | } 34 | 35 | static void write32(void *ptr, uint16_t val) { 36 | char *p = (char *)ptr; 37 | p[0] = val; 38 | p[1] = val>>8; 39 | p[2] = val>>16; 40 | p[3] = val>>24; 41 | } 42 | 43 | static int rgb_encode(uint8_t *dest, const Pixel *src, int w) { 44 | int x; 45 | for (x = 0; x < w; x++) { 46 | *dest++ = src->cp.b; 47 | *dest++ = src->cp.g; 48 | *dest++ = src->cp.r; 49 | src++; 50 | } 51 | return w * 3; 52 | } 53 | 54 | void pixbuf_export_bmp(const PixBuf *pb, const char *filename) { 55 | int y; 56 | uint8_t row[pb->width*4]; 57 | uint8_t header[54]; 58 | const Pixel *pic = pb->array + (pb->height-1)*pb->width; 59 | FILE *out = fopen(filename, "wb"); 60 | if (!out) { 61 | perror(filename); 62 | exit(1); 63 | } 64 | 65 | write16(header, 19778); 66 | write32(header + 2, pb->width*pb->height*3+14+40); 67 | write32(header + 6, 0); 68 | write32(header + 10, 14+40); 69 | 70 | write32(header + 14, 40); 71 | write32(header + 18, pb->width); 72 | write32(header + 22, pb->height); 73 | write16(header + 26, 1); 74 | write16(header + 28, 24); 75 | write32(header + 30, 0); 76 | write32(header + 34, pb->width*pb->height*3); 77 | write32(header + 38, 3780); 78 | write32(header + 42, 3780); 79 | write32(header + 46, 0x0); 80 | write32(header + 50, 0x0); 81 | 82 | fwrite(&header, sizeof(header), 1, out); 83 | for (y = 0; y < pb->height; y++) { 84 | fwrite(row, rgb_encode(row, pic, pb->width), 1, out); 85 | pic -= pb->width; 86 | } 87 | fclose(out); 88 | } 89 | 90 | PixBuf *pixbuf_new(int width, int height) { 91 | PixBuf *pb = malloc(sizeof(PixBuf)); 92 | MEM_ERROR(pb); 93 | pb->_ptr = malloc(width * height * sizeof(Pixel) + 15); 94 | MEM_ERROR(pb->_ptr); 95 | intptr_t adr = (intptr_t)pb->_ptr; 96 | adr = (adr+15)&-16; 97 | pb->array = (Pixel*)adr; 98 | pb->width = width; 99 | pb->height = height; 100 | return pb; 101 | } 102 | 103 | void pixbuf_delete(PixBuf *pb) { 104 | free(pb->_ptr); 105 | free(pb); 106 | } 107 | -------------------------------------------------------------------------------- /pixbuf.h: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------------------------------- 2 | Management of a basic pixel buffer with export to BMP file format. 3 | by Zerkman / Sector One 4 | ------------------------------------------------------------------- */ 5 | 6 | /* Copyright (c) 2012-2025 Francois Galea 7 | * This program is free software. It comes without any warranty, to 8 | * the extent permitted by applicable law. You can redistribute it 9 | * and/or modify it under the terms of the Do What The Fuck You Want 10 | * To Public License, Version 2, as published by Sam Hocevar. See 11 | * the COPYING file or http://www.wtfpl.net/ for more details. */ 12 | 13 | #ifndef _PIXBUF_H_ 14 | #define _PIXBUF_H_ 15 | 16 | #include 17 | 18 | typedef union pixel { 19 | uint32_t rgb; 20 | struct { uint8_t a, r, g, b; } cp; 21 | } Pixel; 22 | 23 | typedef struct _pixbuf { 24 | int width; 25 | int height; 26 | Pixel *array; 27 | void *_ptr; 28 | } PixBuf; 29 | 30 | 31 | PixBuf *pixbuf_new(int width, int height); 32 | void pixbuf_delete(PixBuf *pb); 33 | void pixbuf_export_bmp(const PixBuf *pb, const char *filename); 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /release: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | VERSION=1.1 4 | DEST=mpp-${VERSION} 5 | SRC="bmp2mpp.c mpp2bmp.c pixbuf.c pixbuf.h \ 6 | mppview.s mppdec.s mode0.s mode1.s mode2.s mode3.s" 7 | WCC=i686-w64-mingw32-gcc 8 | 9 | mkdir -p ${DEST}/src || exit 1 10 | cp ${SRC} ${DEST}/src || exit 1 11 | cp README.md COPYING Changelog ${DEST} || exit 1 12 | 13 | make clean 14 | make mppview.tos || exit 1 15 | cp mppview.tos ${DEST}/mppview.ttp 16 | 17 | make CC=$WCC CFLAGS=-O3 || exit 1 18 | cp mpp2bmp ${DEST}/mpp2bmp.exe 19 | cp bmp2mpp ${DEST}/bmp2mpp.exe 20 | make clean 21 | 22 | vc -O1 bmp2mpp.c -o ${DEST}/bmp2mpp.ttp -lm || exit 1 23 | vc -O1 -c99 mpp2bmp.c pixbuf.c -o ${DEST}/mpp2bmp.ttp -lm 24 | 25 | exit 0 26 | --------------------------------------------------------------------------------