1 /**
2  * View binary file as image
3  *
4  * License:
5  *   This Source Code Form is subject to the terms of
6  *   the Mozilla Public License, v. 2.0. If a copy of
7  *   the MPL was not distributed with this file, You
8  *   can obtain one at http://mozilla.org/MPL/2.0/.
9  *
10  * Authors:
11  *   Vladimir Panteleev <ae@cy.md>
12  */
13 
14 module ae.demo.binview.binview;
15 
16 import core.runtime;
17 import core.time;
18 
19 import std.algorithm.comparison;
20 import std.algorithm.searching;
21 import std.conv;
22 import std.digest.crc;
23 import std.exception;
24 import std.format;
25 import std.math;
26 import std.mmfile;
27 import std.stdio;
28 
29 import ae.ui.app.application;
30 import ae.ui.app.main;
31 import ae.ui.shell.shell;
32 import ae.ui.shell.sdl2.shell;
33 import ae.ui.video.bmfont;
34 import ae.ui.video.renderer;
35 import ae.ui.video.sdl2.video;
36 import ae.utils.fps;
37 import ae.utils.graphics.fonts.draw;
38 import ae.utils.graphics.fonts.font8x8;
39 import ae.utils.math;
40 import ae.utils.graphics.image;
41 import ae.utils.meta;
42 
43 final class MyApplication : Application
44 {
45 	override string getName() { return "Demo/BinView"; }
46 	override string getCompanyName() { return "CyberShadow"; }
47 
48 	Shell shell;
49 
50 	MmFile f;
51 
52 	this()
53 	{
54 		enforce(Runtime.args.length == 2, "Usage: binview FILENAME");
55 		f = new MmFile(Runtime.args[1]);
56 	}
57 
58 	size_t offset = 0;
59 	uint width = 256, height;
60 
61 	enum maxFramesToRender = 3; // include possible back-buffers
62 	uint dirty = maxFramesToRender;
63 	uint lastWidth, lastHeight;
64 	uint bpp = 1;
65 	uint zoom = 4;
66 
67 	override void render(Renderer s)
68 	{
69 		if (s.width != lastWidth && s.height != lastHeight)
70 			dirty = maxFramesToRender;
71 		if (dirty == 0)
72 			return;
73 
74 		s.clear();
75 		this.height = s.height / zoom;
76 
77 		shell.setCaption(format("Offset = 0x%08X Width=0x%X (%d) BPP=%d", offset, width, width, bpp));
78 
79 		auto start = min(offset, f.length);
80 		auto length = width * height;
81 		auto end = min(offset + length * bpp, f.length);
82 
83 		auto data = f[start .. end];
84 		data = data[0 .. $ - $ % bpp];
85 
86 		foreach (i; 0 .. data.length / bpp)
87 		{
88 			auto bytes = cast(ubyte[])data[i * bpp .. (i+1) * bpp];
89 			if (bytes.canFind!identity) // leave 0 as black
90 			{
91 				BGRX p;
92 				if (bpp > 3)
93 				{
94 					auto c = crc32Of(bytes);
95 					p = BGRX(c[0], c[1], c[2]);
96 				}
97 				else
98 				{
99 					ubyte[3] channels;
100 					foreach (n, ref c; channels)
101 						c = bytes[n % $];
102 					p = BGRX(channels[0], channels[1], channels[2]);
103 				}
104 
105 				auto x = cast(int)(i % width);
106 				auto y = cast(int)(i / width);
107 				s.fillRect(x * zoom, y * zoom, (x+1) * zoom, (y+1) * zoom, p);
108 			}
109 		}
110 
111 		dirty--;
112 		lastWidth = s.width;
113 		lastHeight = s.height;
114 	}
115 
116 	override int run(string[] args)
117 	{
118 		shell = new SDL2Shell(this);
119 		shell.video = new SDL2SoftwareVideo();
120 		shell.run();
121 		shell.video.shutdown();
122 		return 0;
123 	}
124 
125 	void navigate(uint bytes, bool forward)
126 	{
127 		if (forward)
128 			offset = min(offset + bytes, f.length);
129 		else
130 			offset = offset > bytes ? offset - bytes : 0;
131 	}
132 
133 	override void handleKeyDown(Key key, dchar character)
134 	{
135 		switch (key)
136 		{
137 			case Key.esc:
138 				shell.quit();
139 				break;
140 			case Key.left    : navigate(          1         , false); break;
141 			case Key.right   : navigate(          1         , true ); break;
142 			case Key.up      : navigate(width * bpp         , false); break;
143 			case Key.down    : navigate(width * bpp         , true ); break;
144 			case Key.pageUp  : navigate(width * height * bpp, false); break;
145 			case Key.pageDown: navigate(width * height * bpp, true ); break;
146 			case Key.home    : width = width ? width-1 : 0; break;
147 			case Key.end     : width++; break;
148 			default:
149 				switch (character)
150 				{
151 					case '1':
152 						..
153 					case '9':
154 						bpp = character - '0';
155 						break;
156 					case '+':
157 					case '=':
158 						zoom++;
159 						break;
160 					case '-':
161 						if (zoom > 1)
162 							zoom--;
163 						break;
164 					default:
165 						return;
166 				}
167 		}
168 		dirty = maxFramesToRender;
169 	}
170 
171 	override void handleMouseMove(uint x, uint y, MouseButtons buttons)
172 	{
173 		auto addr = offset + ((y / zoom) * width + (x / zoom)) * bpp;
174 		shell.setCaption(format("x=%d y=%d addr=%08X data=%(%02X %)",
175 				x / zoom, y / zoom, addr, (addr+bpp) < f.length ? cast(ubyte[])f[addr..addr+bpp] : null));
176 	}
177 
178 	override void handleQuit()
179 	{
180 		shell.quit();
181 	}
182 }
183 
184 shared static this()
185 {
186 	createApplication!MyApplication();
187 }