├── .gitignore
├── README.md
├── css
└── main.css
├── imgs
├── battletiles.PNG
├── fallback.png
├── tiles.png
└── tiles0.png
├── index.html
├── js
├── background.js
├── main.js
└── sav_parser.js
└── utlities
├── css
└── hex.css
├── hexViewer.html
└── js
└── hexViewer.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/*
3 | notes/*
4 | js/lib/*
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Readme for Pokémon Save File Reader #
2 |
3 | by Lyndon Armitage
4 |
5 |
6 | This is a HTML5 implementation of a Pokémon Red, Blue and Yellow save game viewer (and possibly editor in the future).
7 | Made for fun, and educational purposes only.
8 |
9 | You can see it in action here:
10 | [lyndon.codes/assets/html5-pokemon-save-reader/](https://lyndon.codes/assets/html5-pokemon-save-reader/)
11 |
12 | There is a more maintained fork of this project by [Daniell Mesquita](https://github.com/DaniellMesquita) that can be seen here:
13 | [github.com/Universav/UniversavPoke](https://github.com/Universav/UniversavPoke)
14 |
15 | ## Current Version: ##
16 |
17 | Currently in the very early stages of development.
18 | Can currently open a .sav file and read the following:
19 | * Trainer Name
20 | * Rival Name
21 | * Trainer ID
22 | * Time Played
23 | * Items in Pocket
24 | * items in PC
25 | * Money
26 | * Checksum
27 | * Current Box selected
28 | * Pokédex Owned and Seen stats
29 | * Basic Location support (x, y, and map id)
30 |
31 | ## Other: ##
32 |
33 | [This page](http://bulbapedia.bulbagarden.net/wiki/Save_data_structure_in_Generation_I) is useful for The structure of Pokémon Red/Blue & Possibly Yellow save files. Although not all of it's data is accurate.
34 |
35 | I have also created a small Hex Viewer using the HTML5 File API and Storage API that let's you view a files contents and write notes.
36 | Note that this may crash your browser for files with a lot of bytes (i.e. bigger files). I have only tested it with .sav files which are 32kb large.
37 |
38 | ### Copyright: ###
39 |
40 | Pokémon © 2002-2015 Pokémon. © 1995-2015 Nintendo/Creatures Inc./GAME FREAK inc. TM, ® and Pokémon character names are trademarks of Nintendo.
41 |
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'VT323', cursive;
3 | }
4 |
5 | #container {
6 | position: relative;
7 | width: 800px;
8 | min-height: 600px;
9 | margin: 0 auto;
10 | border: #000000 solid 1px;
11 | background: url("../imgs/fallback.png") repeat;
12 | }
13 |
14 | .btn {
15 | width: 200px;
16 | height: 100px;
17 | border: #000000 outset 2px;
18 | text-align: center;
19 | line-height: 100px;
20 | border-radius: 20px;
21 | cursor: pointer;
22 | }
23 |
24 | .btn:active {
25 | border: #000000 inset 2px;
26 | }
27 |
28 | #inputLabel {
29 | margin: 10px auto;
30 | background: red;
31 | }
32 |
33 |
34 | #saveButton {
35 | background: blue;
36 | }
37 |
38 | #outputSection {
39 | width: 600px;
40 | min-height: 200px;
41 | border: #000000 groove 2px;
42 | border-radius: 20px;
43 | background: #ffffff;
44 | margin: 10px auto;
45 | padding: 10px;
46 | }
47 |
48 | #error {
49 | text-align: center;
50 | color: red;
51 | font-weight: bold;
52 | }
53 |
54 | h1 {
55 | text-align: center;
56 | font-size: 25px;
57 | }
58 |
59 | h2 {
60 | text-align: center;
61 | font-size: 20px;
62 | }
63 |
64 | #footer {
65 | text-align: center;
66 | font-size: 12px;
67 | }
--------------------------------------------------------------------------------
/imgs/battletiles.PNG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LyndonArmitage/HTML5PokemonSaveReader/c0b30d1b963cfdddb2708f38d6f6a53b56f140b2/imgs/battletiles.PNG
--------------------------------------------------------------------------------
/imgs/fallback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LyndonArmitage/HTML5PokemonSaveReader/c0b30d1b963cfdddb2708f38d6f6a53b56f140b2/imgs/fallback.png
--------------------------------------------------------------------------------
/imgs/tiles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LyndonArmitage/HTML5PokemonSaveReader/c0b30d1b963cfdddb2708f38d6f6a53b56f140b2/imgs/tiles.png
--------------------------------------------------------------------------------
/imgs/tiles0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LyndonArmitage/HTML5PokemonSaveReader/c0b30d1b963cfdddb2708f38d6f6a53b56f140b2/imgs/tiles0.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | HTML5 Pokémon Save File Viewer
5 |
6 |
7 |
8 |
9 |
10 | Pokémon Save File Viewer
11 |
12 |
17 |
18 |
Select a Save File to begin.
19 |
20 |
21 |
24 |
25 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/js/background.js:
--------------------------------------------------------------------------------
1 | /**
2 | Background Generator
3 |
4 | This will generate a good looking background for the Save File Editor using a few sprites and a canvas
5 | **/
6 |
7 | /**
8 | * Returns image data to be used as the background to an element
9 | * @param width
10 | * @param height
11 | */
12 | function setBackground(element ,width, height) {
13 |
14 | function isSupported(){
15 | var elem = document.createElement('canvas');
16 | return !!(elem.getContext && elem.getContext('2d'));
17 | }
18 |
19 | // Returns a random integer between min and max
20 | // Using Math.round() will give you a non-uniform distribution!
21 | function getRandomInt(min, max) {
22 | return Math.floor(Math.random() * (max - min + 1)) + min;
23 | }
24 |
25 | function prob(value) {
26 | return getRandomInt(0, 100) < value;
27 | }
28 |
29 | function create2DArray(rows) {
30 | var arr = [];
31 | for (var i=0;i 0 && map[x-1][y] == ALIVE) {
49 | // left
50 | live ++;
51 | }
52 | if(y < mapHeight-1 && map[x][y+1] == ALIVE) {
53 | // up
54 | live ++;
55 | }
56 | if(y > 0 && map[x][y-1] == ALIVE) {
57 | // down
58 | live ++;
59 | }
60 | if(y < mapHeight-1 && x < mapWidth-1 && map[x+1][y+1] == ALIVE) {
61 | // top right
62 | live ++;
63 | }
64 | if(y > 0 && x < mapWidth-1 && map[x+1][y-1] == ALIVE) {
65 | // bottom right
66 | live ++;
67 | }
68 | if(y < mapHeight-1 && x > 0 && map[x-1][y+1] == ALIVE) {
69 | // top left
70 | live ++;
71 | }
72 | if(y > 0 && x > 0 && map[x-1][y-1] == ALIVE) {
73 | // bottom left
74 | live ++;
75 | }
76 | return live;
77 | }
78 |
79 | // set everything to random
80 | for(var x = 0; x < mapWidth; x ++) {
81 | for(var y = 0; y < mapHeight; y ++) {
82 | if(prob(10)) {
83 | map[x][y] = ALIVE;
84 | }
85 | else {
86 | map[x][y] = DEAD;
87 | }
88 | }
89 | }
90 |
91 | var iterations = 10;
92 | while(iterations > 0) {
93 | var newMap = create2DArray(mapWidth);
94 | for(var x = 0; x < mapWidth; x ++) {
95 | for(var y = 0; y < mapHeight; y ++) {
96 | var cell = map[x][y];
97 | if(cell == ALIVE) {
98 | if(getLiveNeighbours(x, y) < 2) {
99 | newMap[x][y] = DEAD;
100 | }
101 | else {
102 | newMap[x][y] = ALIVE;
103 | }
104 | }
105 | else {
106 | if(getLiveNeighbours(x, y) > 2) {
107 | newMap[x][y] = ALIVE;
108 | }
109 | else {
110 | newMap[x][y] = DEAD;
111 | }
112 | }
113 | }
114 | }
115 | map = newMap;
116 | iterations --;
117 | }
118 |
119 |
120 | return map;
121 | }
122 |
123 | function drawBackground(canvas, tileMap) {
124 | var ctx = canvas.getContext("2d");
125 | var tileSize = 8;
126 |
127 | // run the map generator
128 | var map = generateMap(canvas.width / tileSize, canvas.height / tileSize);
129 |
130 | for(var x = 0; x < canvas.width / tileSize; x ++) {
131 | for(var y = 0; y < canvas.height / tileSize; y ++) {
132 | if(map[x][y] == true) {
133 | ctx.drawImage(tileMap,16, 104, tileSize, tileSize, x*tileSize, y*tileSize, tileSize, tileSize);
134 | }
135 | else {
136 | ctx.drawImage(tileMap,104, 64, tileSize, tileSize, x*tileSize, y*tileSize, tileSize, tileSize);
137 | }
138 | }
139 | }
140 |
141 |
142 |
143 |
144 | }
145 |
146 | if(isSupported()) {
147 | var canvas = document.createElement("canvas");
148 | canvas.width = width;
149 | canvas.height = height;
150 | var tileMap = new Image();
151 | tileMap.onload = function() {
152 | drawBackground(canvas, this);
153 | element.style.background = "url(" + canvas.toDataURL() + ") repeat";
154 | }
155 | tileMap.src = "imgs/tiles.png";
156 | }
157 | else {
158 | element.style.background = "url('../imgs/fallback.png') repeat;";
159 | }
160 |
161 |
162 |
163 | }
164 |
--------------------------------------------------------------------------------
/js/main.js:
--------------------------------------------------------------------------------
1 |
2 | $(document).ready(function() {
3 | loadEvent();
4 | });
5 |
6 | function updateBG() {
7 | var $container = $("#container");
8 | setBackground($container[0], $container.width(), $container.height());
9 | }
10 |
11 | function loadEvent() {
12 | var $container = $("#container");
13 | updateBG();
14 | if(!supportsFileAPI()) {
15 | $container.html("Your browser does not support HTML5's File API. Please update to a better browser.
");
16 | }
17 | else {
18 | // We support HTML5 File API so let's get cooking.
19 | var $fileIn = $("#fileInput");
20 | $fileIn.bind("change", loadFile);
21 | $("#inputLabel").bind("click", function() {
22 | $fileIn.click();
23 | })
24 | }
25 | }
26 |
27 | /**
28 | * Function that returns true if the user's web browser supports the HTML5 File API
29 | * @returns {boolean}
30 | */
31 | function supportsFileAPI() {
32 | return window.File && window.FileReader && window.FileList && window.Blob;
33 | }
34 |
35 | /**
36 | * Ends with method for checking if a string ends with a bit of text
37 | * @param suffix
38 | * @returns {boolean}
39 | */
40 | String.prototype.endsWith = function(suffix) {
41 | return this.indexOf(suffix, this.length - suffix.length) !== -1;
42 | };
43 |
44 | function error(msg) {
45 | $("#outputSection").html(""+msg+"
");
46 | }
47 |
48 | function readFile(savefile) {
49 | var reader = new FileReader();
50 |
51 | reader.onload = (function(theFile) {
52 | return function(e) {
53 | $("#outputSection").html(""+ theFile.name +" loaded
");
54 | var results = parseSav(e.target.result);
55 | if(results.checksum === 0) {
56 | error("Invalid save file provided. Is it a Generation I save file?");
57 | }
58 | else {
59 | console.log(results);
60 | var resultsContents = ""+
61 | "- Trainer Name: " + results.trainerName + "
"+
62 | "- Trainer ID: " + results.trainerID + "
"+
63 | "- Rival Name: " + results.rivalName + "
"+
64 | "- Time Played: " + results.timePlayed.hours +":"+ results.timePlayed.minutes + ":" + results.timePlayed.seconds + "
"+
65 | "- Money: " + results.money + "
"+
66 | "- Checksum: " + results.checksum + "
"+
67 | "- Current PC Box: " + results.currentPCBox + "
";
68 |
69 | function addItemList(label, items) {
70 | resultsContents += "- "+label+": ";
71 | if(items.count > 0) {
72 | var html = "
";
73 | for(var i = 0; i < items.count; i ++) {
74 | var item = items.items[i];
75 | html += "- "+item.name+" x"+item.count+"
";
76 | }
77 | html +="
";
78 | resultsContents += html;
79 | }
80 | resultsContents += " ";
81 | }
82 |
83 | addItemList("Pocket Items", results.pocketItemList);
84 | addItemList("PC Items", results.PCItemList);
85 |
86 | function addPokedexList(label, list) {
87 | var num = 0;
88 | for(var i = 0; i < list.length; i ++) {
89 | if(list.charAt(i) == "1") {
90 | num ++;
91 | }
92 | }
93 | resultsContents += "- "+label+": "+ num +"
";
94 | }
95 |
96 | addPokedexList("Pokédex Seen", results.seenList);
97 | addPokedexList("Pokédex Owned", results.ownedList);
98 |
99 | function addPokemonList(label, list) {
100 | resultsContents += "- "+label+":";
101 | if(list.count > 0) {
102 | resultsContents += "
";
103 | for(var i = 0; i < list.count; i ++) {
104 | resultsContents += "- ";
105 | resultsContents += list.names[i];
106 | resultsContents += "
";
107 | resultsContents += "- Species: "+list.species[i]+"
";
108 | resultsContents += "- Original Trainer: "+list.OTNames[i]+"
";
109 | resultsContents += "- Type 1: "+list.pokemon[i].type1+"
";
110 | resultsContents += "- Type 2: "+list.pokemon[i].type2+"
";
111 | resultsContents += "- Current HP: "+list.pokemon[i].currentHp+"
";
112 | resultsContents += "- Exp: "+list.pokemon[i].exp+"
";
113 | if(list.pokemon[i].isParty) {
114 | resultsContents += "- Level: "+list.pokemon[i].partyLevel+"
";
115 | }
116 | else {
117 | resultsContents += "- Level: "+list.pokemon[i].level+"
";
118 | }
119 | resultsContents += "
";
120 |
121 | resultsContents += " ";
122 | }
123 | resultsContents += "
";
124 | }
125 | resultsContents += " ";
126 | }
127 |
128 | addPokemonList("Party Pokémon", results.partyList);
129 | addPokemonList("Current Box Pokémon", results.currentBoxList);
130 |
131 | resultsContents += "
";
132 | $("#outputSection").append(resultsContents);
133 |
134 | }
135 | updateBG();
136 | };
137 | })(savefile);
138 | reader.readAsBinaryString(savefile);
139 | }
140 |
141 | function loadFile(evt) {
142 | evt = evt.originalEvent || evt;
143 | var savefile = evt.target.files[0];
144 | // save files are 32kb large and end in .sav
145 | if(savefile != null && savefile.size >= 32768 && savefile.name.endsWith(".sav")) {
146 | readFile(savefile);
147 | }
148 | else {
149 | error("Invalid save file provided. File size or extension is wrong.");
150 | }
151 | }
--------------------------------------------------------------------------------
/js/sav_parser.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | JavaScript Based Pokemon Save File viewer by Lyndon Armitage 2013
4 | ---
5 | This will make use of HTML5's File API so will not work in older browsers.
6 |
7 | */
8 |
9 | /**
10 | * Save Data Parser.
11 | * Will Take in a binary string and return an object representing the save data gleamed from it.
12 | * Currently only supports Generation 1 save files (Yellow, Red and Blue)
13 | * @param data
14 | * @returns {{trainerName: *, rivalName: *, trainerID: *, timePlayed: *, pocketItemList: *, PCItemList: *, checksum: *}}
15 | */
16 | function parseSav(data) {
17 | // lets test getting the trainer name data
18 | // Currently just output the hex, it works so next step is to translate into text
19 | function getTrainerName() {
20 | var offset = 0x2598; // trainer name offset
21 | var size = 8;
22 | return getTextString(offset, size);
23 | }
24 |
25 | function getRivalName() {
26 | var offset = 0x25F6; // rival name offset
27 | var size = 8;
28 | return getTextString(offset, size);
29 | }
30 |
31 | function getTrainerID() {
32 | var offset = 0x2605;
33 | var size = 2;
34 | return hex2int(offset, size);
35 | }
36 |
37 | function getTimePlayed() {
38 | var offset = 0x2CED;
39 | return {
40 | hours : hex2int(offset, 1),
41 | minutes : hex2int(offset+2, 1),
42 | seconds : hex2int(offset+3, 1),
43 | frames : hex2int(offset+4, 1)
44 | };
45 | }
46 |
47 | function getPocketItemList() {
48 | // Can hold 20 items making the size 42 because: Capacity * 2 + 2 = 42
49 | return getItemList(0x25C9, 20);
50 | }
51 |
52 | function getPCItemList() {
53 | // Can hold 50 items making the size 102 because: Capacity * 2 + 2 = 102
54 | // Offset seems to be 0x27E6 not 0x27E7 as said on bulbapedia
55 | return getItemList(0x27E6, 50);
56 | }
57 |
58 | function getChecksum() {
59 | return hex2int(0x3523, 1);
60 | }
61 |
62 | function getMoney() {
63 | // makes use of packed BCD
64 | // Represents how much money the character has.
65 | // The figure is a 6-digit number, 2 digits per byte, encoded as binary-coded decimal,
66 | // where each digit is allocated a full 4 bits.
67 | var offset = 0x25F3;
68 | var size = 3; // 3 bytes because each digit comes from a nibble
69 | var out = "";
70 | var shouldAdd = false;
71 | for(var i = 0; i < size; i ++) {
72 | var byteVal = data.charCodeAt(offset+i);
73 | var digit1 = byteVal >> 4;
74 | var digit2 = byteVal & 0xF;
75 | // Check if we should add 0s (for middle of number)
76 | if(shouldAdd || digit1 > 0) {
77 | out += digit1;
78 | shouldAdd = true;
79 | }
80 | if(shouldAdd || digit2 > 0) {
81 | out += digit2;
82 | shouldAdd = true;
83 | }
84 | }
85 | return out;
86 | }
87 |
88 | function getCurrentPCBox() {
89 | return lowNibble(hex2int(0x284C, 1)) + 1;
90 | }
91 |
92 | function getSeenList() {
93 | return getPokedexList(0x25B6);
94 | }
95 |
96 | function getOwnedList() {
97 | return getPokedexList(0x25A3);
98 | }
99 |
100 | function getPlayerPosition() {
101 | var obj = {
102 | x : hex2int(0x260E, 1),
103 | y : hex2int(0x260D, 1),
104 | mapNum : hex2int(0x260A, 1)
105 | };
106 | return obj;
107 | }
108 |
109 | function getPartyList() {
110 | return getPokemonList(0x2F2C, 6, true);
111 | }
112 |
113 | function getBoxList(boxNumber) {
114 | var boxMap = {
115 | 1 : 0x4000,
116 | 2 : 0x4462,
117 | 3 : 0x48C4,
118 | 4 : 0x4D26,
119 | 5 : 0x5188,
120 | 6 : 0x55EA,
121 | 7 : 0x6000,
122 | 8 : 0x6462,
123 | 9 : 0x68C4,
124 | 10 : 0x6D26,
125 | 11 : 0x7188,
126 | 12 : 0x75EA
127 | };
128 | var offset = boxMap[boxNumber];
129 | console.log("! " + offset.toString(16));
130 | return getPokemonList(offset, 20, false);
131 | }
132 |
133 | function getCurrentBoxList() {
134 | return getPokemonList(0x30C0, 20, false);
135 | }
136 |
137 | function getPokemonList(offset, capacity, isParty) {
138 | var size = isParty ? 44 : 33;
139 | var totalSize = capacity * (size + 23) + 2;
140 | var count = hex2int(offset, 1);
141 |
142 | var list = {
143 | count : hex2int(offset, 1),
144 | species : [],
145 | pokemon : [],
146 | OTNames : [],
147 | names : []
148 | };
149 |
150 | // we will use count here as it tells us the amount actually present so we don't need to look for 0xFF
151 | for(var i = 0; i < count; i ++) {
152 | list.species[i] = getSpeciesFromIndex(hex2int(offset + 1 + i, 1));
153 | list.pokemon[i] = new Pokemon((offset + 2 + capacity) + (i * size), isParty)
154 | list.OTNames[i] = getTextString( ((offset + 2 + capacity) + ((capacity-1) * size)) + size + (i*11) , 10);
155 | list.names[i] = getTextString( (((offset + 2 + capacity) + ((capacity-1) * size)) + size + ((capacity-1)*11)) + ((i+1)*11) , 10);
156 | }
157 |
158 | return list;
159 | }
160 |
161 | /**
162 | * Returns a binary string where a 1 represents that the Pokemon with that index is present.
163 | * @param offset
164 | * @returns {string}
165 | */
166 | function getPokedexList(offset) {
167 | var length = 19; // 19 bytes long = 152 possible bits
168 | var binaryString = "";
169 | for(var i = length; i > 0; i --) {
170 | var str = data.charCodeAt(offset + i).toString(2);
171 | var padding = "";
172 | if(str.length < 8) {
173 | var dif = 8 - str.length;
174 | while(dif > 0) {
175 | padding += "0";
176 | dif --;
177 | }
178 | }
179 | binaryString += str + padding;
180 | }
181 | return binaryString.split("").reverse().join("");
182 | }
183 |
184 | function getItemList(offset, maxSize) {
185 |
186 | // Two extra bytes:
187 | // The byte at offset 0x00 is the total Count of different items in the list
188 | // The byte following the last item entry, according to Count, must always be a terminator, which is byte value 0xFF.
189 |
190 | var obj = {
191 | count : hex2int(offset, 1),
192 | items : []
193 | };
194 |
195 | offset ++;
196 | // Each entry is 2 bytes large, first byte is the Count of that item, second is it's Index
197 | for(var i = 0; i < obj.count && i < maxSize; i ++) {
198 | var itemIndex = hex2int(offset+(i*2), 1);
199 | var itemCount = hex2int(offset+(i*2)+1, 1);
200 | var name = getItemNameFromHexIndex(data.charCodeAt(offset+(i*2)));
201 | obj.items.push({
202 | count : itemCount,
203 | index : itemIndex,
204 | name : name
205 | });
206 | }
207 |
208 | return obj;
209 | }
210 |
211 | function getTextString(offset, size) {
212 | var output = "";
213 | for(var i = 0; i < size; i ++) {
214 | var code = data.charCodeAt(offset + i);
215 | if(code == 0x50) break; // terminate string
216 | output += getChar(code);
217 | }
218 | return output;
219 | }
220 |
221 | function getChar(hex) {
222 | var charMap = {
223 | 0x50 : "\0", 0x7F : " ",
224 |
225 | 0x80 : "A", 0x81 : "B", 0x82 : "C", 0x83 : "D", 0x84 : "E",
226 | 0x85 : "F", 0x86 : "G", 0x87 : "H", 0x88 : "I", 0x89 : "J",
227 | 0x8A : "K", 0x8B : "L", 0x8C : "M", 0x8D : "N", 0x8E : "O",
228 | 0x8F : "P", 0x90 : "Q", 0x91 : "R", 0x92 : "S", 0x93 : "T",
229 | 0x94 : "U", 0x95 : "V", 0x96 : "W", 0x97 : "X", 0x98 : "Y",
230 | 0x99 : "Z", 0x9A : "(", 0x9B : ")", 0x9C : ":", 0x9D : ";",
231 | 0x9E : "[", 0x9F : "]",
232 |
233 | 0xA0 : "a", 0xA1 : "b", 0xA2 : "c", 0xA3 : "d", 0xA4 : "e",
234 | 0xA5 : "f", 0xA6 : "g", 0xA7 : "h", 0xA8 : "i", 0xA9 : "j",
235 | 0xAA : "k", 0xAB : "l", 0xAC : "m", 0xAD : "n", 0xAE : "o",
236 | 0xAF : "p", 0xB0 : "q", 0xB1 : "r", 0xB2 : "s", 0xB3 : "t",
237 | 0xB4 : "u", 0xB5 : "v", 0xB6 : "w", 0xB7 : "x", 0xB8 : "y",
238 | 0xB9 : "z",
239 |
240 | 0xE1 : "PK", 0xE2 : "MN", 0xE3 : "-",
241 | 0xE6 : "?", 0xE7 : "!", 0xE8 : ".",
242 |
243 | 0xF1 : "*",
244 | 0xF3 : "/", 0xF4 : ",",
245 |
246 | 0xF6 : "0", 0xF7 : "1", 0xF8 : "2", 0xF9 : "3", 0xFA : "4",
247 | 0xFB : "5", 0xFC : "6", 0xFD : "7", 0xFE : "8", 0xFF : "9"
248 | };
249 | return charMap[hex];
250 | }
251 |
252 | function getItemNameFromHexIndex(hex) {
253 | var itemMap = {
254 | 0x00 : "Nothing", 0x01 : "Master Ball", 0x02 : "Ultra Ball", 0x03 : "Great Ball", 0x04 : "Poké Ball",
255 | 0x05 : "Town Map", 0x06 : "Bicycle", 0x07 : "?????", 0x08 : "Safari Ball", 0x09 : "Pokédex",
256 | 0x0A : "Moon Stone", 0x0B : "Antidote", 0x0C : "Burn Heal", 0x0D : "Ice Heal", 0x0E : "Awakening",
257 | 0x0F : "Parlyz Heal", 0x10 : "Full Restore", 0x11 : "Max Potion", 0x12 : "Hyper Potion", 0x13 : "Super Potion",
258 | 0x14 : "Potion", 0x15 : "BoulderBadge", 0x16 : "CascadeBadge", 0x17 : "ThunderBadge", 0x18 : "RainbowBadge",
259 | 0x19 : "SoulBadge", 0x1A : "MarshBadge", 0x1B : "VolcanoBadge", 0x1C : "EarthBadge", 0x1D : "Escape Rope",
260 | 0x1E : "Repel", 0x1F : "Old Amber", 0x20 : "Fire Stone", 0x21 : "Thunderstone", 0x22 : "Water Stone",
261 | 0x23 : "HP Up", 0x24 : "Protein", 0x25 : "Iron", 0x26 : "Carbos", 0x27 : "Calcium",
262 | 0x28 : "Rare Candy", 0x29 : "Dome Fossil", 0x2A : "Helix Fossil", 0x2B : "Secret Key", 0x2C : "?????",
263 | 0x2D : "Bike Voucher", 0x2E : "X Accuracy", 0x2F : "Leaf Stone", 0x30 : "Card Key", 0x31 : "Nugget",
264 | 0x32 : "PP Up", 0x33 : "Poké Doll", 0x34 : "Full Heal", 0x35 : "Revive", 0x36 : "Max Revive",
265 | 0x37 : "Guard Spec.", 0x38 : "Super Repel", 0x39 : "Max Repel", 0x3A : "Dire Hit", 0x3B : "Coin",
266 | 0x3C : "Fresh Water", 0x3D : "Soda Pop", 0x3E : "Lemonade", 0x3F : "S.S. Ticket", 0x40 : "Gold Teeth",
267 | 0x41 : "X Attack", 0x42 : "X Defend", 0x43 : "X Speed", 0x44 : "X Special", 0x45 : "Coin Case",
268 | 0x46 : "Oak's Parcel", 0x47 : "Itemfinder", 0x48 : "Silph Scope", 0x49 : "Poké Flute", 0x4A : "Lift Key",
269 | 0x4B : "Exp. All", 0x4C : "Old Rod", 0x4D : "Good Rod", 0x4E : "Super Rod", 0x4F : "PP Up",
270 | 0x50 : "Ether", 0x51 : "Max Ether", 0x52 : "Elixir", 0x53 : "Max Elixir"
271 | };
272 | // Add all 5 of the HMs
273 | for(var i = 0; i < 5; i ++) {
274 | itemMap[0xC4+i] = "HM0" + (1+i);
275 | }
276 | // Add all 55 og the TMs
277 | for(i = 0; i < 55; i ++) {
278 | var num = (1+i);
279 | if(num < 10) num = "0" + num;
280 | itemMap[0xC9+i] = "TM" + num;
281 | }
282 | return itemMap[hex];
283 | }
284 |
285 | /**
286 | * Returns how friendly Pikachu is to you
287 | * @returns {*}
288 | */
289 | function getPikachuFriendship() {
290 | return hex2int(0x271C, 1);
291 | }
292 |
293 | function hex2int(offset, size) {
294 | var val = "";
295 | for(var i = 0; i < size; i ++) {
296 | var d = data.charCodeAt(offset + i).toString(16);
297 | if(d.length < 2) d = "0" + d; // append leading 0, should not break anything from previous version
298 | val += d;
299 | }
300 | return parseInt(val, 16);
301 | }
302 |
303 | function lowNibble(val) {
304 | return val & 0x0F;
305 | }
306 |
307 | function highNibble(val) {
308 | return (val >> 4) & 0x0F;
309 | }
310 |
311 | function getPokemonType(index) {
312 | var types = {
313 | 0x00: "Normal",
314 | 0x01: "Fighting",
315 | 0x02: "Flying",
316 | 0x03: "Poison",
317 | 0x04: "Ground",
318 | 0x05: "Rock",
319 | 0x07: "Bug",
320 | 0x08: "Ghost",
321 | 0x14: "Fire",
322 | 0x15: "Water",
323 | 0x16: "Grass",
324 | 0x17: "Electric",
325 | 0x18: "Psychic",
326 | 0x19: "Ice",
327 | 0x1A: "Dragon"
328 | };
329 | return types[index];
330 | }
331 |
332 | function getSpeciesFromIndex(index) {
333 | var species = {
334 | 0x01: " Rhydon", 0x02: "Kangaskhan", 0x03: "Nidoran♂", 0x04: "Clefairy", 0x05: "Spearow",
335 | 0x06: "Voltorb", 0x07: "Nidoking", 0x08: "Slowbro", 0x09: "Ivysaur", 0x0A: "Exeggutor",
336 | 0x0B: "Lickitung", 0x0C: "Exeggcute", 0x0D: "Grimer", 0x0E: "Gengar", 0x0F: "Nidoran♀",
337 | 0x10: "Nidoqueen", 0x11: "Cubone", 0x12: "Rhyhorn", 0x13: "Lapras", 0x14: "Arcanine",
338 | 0x15: "Mew", 0x16: "Gyarados", 0x17: "Shellder", 0x18: "Tentacool", 0x19: "Gastly",
339 | 0x1A: "Scyther", 0x1B: "Staryu", 0x1C: "Blastoise", 0x1D: "Pinsir", 0x1E: "Tangela",
340 | 0x1F: "Missingno.", 0x20: "Missingno.", 0x21: "Growlithe", 0x22: "Onix", 0x23: "Fearow",
341 | 0x24: "Pidgey", 0x25: "Slowpoke", 0x26: "Kadabra", 0x27: "Graveler", 0x28: "Chansey",
342 | 0x29: "Machoke", 0x2A: "Mr. Mime", 0x2B: "Hitmonlee", 0x2C: "Hitmonchan", 0x2D: "Arbok",
343 | 0x2E: "Parasect", 0x2F: "Psyduck", 0x30: "Drowzee", 0x31: "Golem", 0x32: "Missingno.",
344 | 0x33: "Magmar", 0x34: "Missingno.", 0x35: "Electabuzz", 0x36: "Magneton", 0x37: "Koffing",
345 | 0x38: "Missingno.", 0x39: "Mankey", 0x3A: "Seel", 0x3B: "Diglett", 0x3C: "Tauros",
346 | 0x3D: "Missingno.",
347 | 0x3E: "Missingno.",
348 | 0x3F: "Missingno.",
349 | 0x40: "Farfetch'd",
350 | 0x41: "Venonat",
351 | 0x42: "Dragonite",
352 | 0x43: "Missingno.",
353 | 0x44: "Missingno.",
354 | 0x45: "Missingno.",
355 | 0x46: "Doduo",
356 | 0x47: "Poliwag",
357 | 0x48: "Jynx",
358 | 0x49: "Moltres",
359 | 0x4A: "Articuno",
360 | 0x4B: "Zapdos",
361 | 0x4C: "Ditto",
362 | 0x4D: "Meowth",
363 | 0x4E: "Krabby",
364 | 0x4F: "Missingno.",
365 | 0x50: "Missingno.",
366 | 0x51: "Missingno.",
367 | 0x52: "Vulpix",
368 | 0x53: "Ninetales",
369 | 0x54: "Pikachu",
370 | 0x55: "Raichu",
371 | 0x56: "Missingno.",
372 | 0x57: "Missingno.",
373 | 0x58: "Dratini",
374 | 0x59: "Dragonair",
375 | 0x5A: "Kabuto",
376 | 0x5B: "Kabutops",
377 | 0x5C: "Horsea",
378 | 0x5D: "Seadra",
379 | 0x5E: "Missingno.",
380 | 0x5F: "Missingno.",
381 | 0x60: "Sandshrew",
382 | 0x61: "Sandslash",
383 | 0x62: "Omanyte",
384 | 0x63: "Omastar",
385 | 0x64: "Jigglypuff",
386 | 0x65: "Wigglytuff",
387 | 0x66: "Eevee",
388 | 0x67: "Flareon",
389 | 0x68: "Jolteon",
390 | 0x69: "Vaporeon",
391 | 0x6A: "Machop",
392 | 0x6B: "Zubat",
393 | 0x6C: "Ekans",
394 | 0x6D: "Paras",
395 | 0x6E: "Poliwhirl",
396 | 0x6F: "Poliwrath",
397 | 0x70: "Weedle",
398 | 0x71: "Kakuna",
399 | 0x72: "Beedrill",
400 | 0x73: "Missingno.",
401 | 0x74: "Dodrio",
402 | 0x75: "Primeape",
403 | 0x76: "Dugtrio",
404 | 0x77: "Venomoth",
405 | 0x78: "Dewgong",
406 | 0x79: "Missingno.",
407 | 0x7A: "Missingno.",
408 | 0x7B: "Caterpie",
409 | 0x7C: "Metapod",
410 | 0x7D: "Butterfree",
411 | 0x7E: "Machamp",
412 | 0x7F: "Missingno.",
413 | 0x80: "Golduck",
414 | 0x81: "Hypno",
415 | 0x82: "Golbat",
416 | 0x83: "Mewtwo",
417 | 0x84: "Snorlax",
418 | 0x85: "Magikarp",
419 | 0x86: "Missingno.",
420 | 0x87: "Missingno.",
421 | 0x88: "Muk",
422 | 0x89: "Missingno.",
423 | 0x8A: "Kingler",
424 | 0x8B: "Cloyster",
425 | 0x8C: "Missingno.",
426 | 0x8D: "Electrode",
427 | 0x8E: "Clefable",
428 | 0x8F: "Weezing",
429 | 0x90: "Persian",
430 | 0x91: "Marowak",
431 | 0x92: "Missingno.",
432 | 0x93: "Haunter",
433 | 0x94: "Abra",
434 | 0x95: "Alakazam",
435 | 0x96: "Pidgeotto",
436 | 0x97: "Pidgeot",
437 | 0x98: "Starmie",
438 | 0x99: "Bulbasaur",
439 | 0x9A: "Venusaur",
440 | 0x9B: "Tentacruel",
441 | 0x9C: "Missingno.",
442 | 0x9D: "Goldeen",
443 | 0x9E: "Seaking",
444 | 0x9F: "Missingno.",
445 | 0xA0: "Missingno.",
446 | 0xA1: "Missingno.",
447 | 0xA2: "Missingno.",
448 | 0xA3: "Ponyta",
449 | 0xA4: "Rapidash",
450 | 0xA5: "Rattata",
451 | 0xA6: "Raticate",
452 | 0xA7: "Nidorino",
453 | 0xA8: "Nidorina",
454 | 0xA9: "Geodude",
455 | 0xAA: "Porygon",
456 | 0xAB: "Aerodactyl",
457 | 0xAC: "Missingno.",
458 | 0xAD: "Magnemite",
459 | 0xAE: "Missingno.",
460 | 0xAF: "Missingno.",
461 | 0xB0: "Charmander",
462 | 0xB1: "Squirtle",
463 | 0xB2: "Charmeleon",
464 | 0xB3: "Wartortle",
465 | 0xB4: "Charizard",
466 | 0xB5: "Missingno.",
467 | 0xB6: "Missingno.",
468 | 0xB7: "Missingno.",
469 | 0xB8: "Missingno.",
470 | 0xB9: "Oddish",
471 | 0xBA: "Gloom",
472 | 0xBB: "Vileplume",
473 | 0xBC: "Bellsprout",
474 | 0xBD: "Weepinbell",
475 | 0xBE: "Victreebel"
476 | };
477 |
478 | return species[index];
479 | }
480 |
481 | /**
482 | * Constructor for new Pokemon
483 | * @param startOffset
484 | * @param isPartyMember
485 | * @constructor
486 | */
487 | function Pokemon(startOffset, isPartyMember) {
488 | this.index = hex2int(startOffset, 1);
489 | this.species = getSpeciesFromIndex(this.index); // derived from index
490 | this.currentHp = hex2int(startOffset + 0x01, 2);
491 | this.level = hex2int(startOffset + 0x03, 1);
492 | this.status = hex2int(startOffset + 0x04, 1);
493 | this.type1Index = hex2int(startOffset + 0x05, 1);
494 | this.type2Index = hex2int(startOffset + 0x06, 1);
495 | this.type1 = getPokemonType(this.type1Index);
496 | this.type2 = getPokemonType(this.type2Index);
497 | this.catchRate = hex2int(startOffset + 0x07, 1);
498 | this.move1Index = hex2int(startOffset + 0x08, 1);
499 | this.move2Index = hex2int(startOffset + 0x09, 1);
500 | this.move3Index = hex2int(startOffset + 0x0A, 1);
501 | this.move4Index = hex2int(startOffset + 0x0B, 1);
502 | this.ownerID = hex2int(startOffset + 0x0C, 2);
503 | this.exp = hex2int(startOffset + 0x0E, 3);
504 | this.hpEV = hex2int(startOffset + 0x11, 2);
505 | this.attackEV = hex2int(startOffset + 0x13, 2);
506 | this.defenseEV = hex2int(startOffset + 0x15, 2);
507 | this.speedEV = hex2int(startOffset + 0x17, 2);
508 | this.specialEV = hex2int(startOffset + 0x19, 2);
509 | this.IV = hex2int(startOffset + 0x1B, 2);
510 | this.move1PP = hex2int(startOffset + 0x1D, 1);
511 | this.move2PP = hex2int(startOffset + 0x1E, 1);
512 | this.move3PP = hex2int(startOffset + 0x1F, 1);
513 | this.move4PP = hex2int(startOffset + 0x20, 1);
514 | if(isPartyMember) {
515 | this.partyLevel = hex2int(startOffset + 0x21, 1);
516 | this.partyMaxHp = hex2int(startOffset + 0x22, 2);
517 | this.partyAttack = hex2int(startOffset + 0x24, 2);
518 | this.partyDefense = hex2int(startOffset + 0x26, 2);
519 | this.partySpeed = hex2int(startOffset + 0x28, 2);
520 | this.partySpecial = hex2int(startOffset + 0x2A, 2);
521 | this.isParty = true;
522 | }
523 | else {
524 | this.isParty = false;
525 | }
526 | }
527 |
528 | // return an object containing the data in an easy to manipulate format
529 | return {
530 | trainerName: getTrainerName(),
531 | rivalName: getRivalName(),
532 | trainerID: getTrainerID(),
533 | timePlayed : getTimePlayed(),
534 | pocketItemList : getPocketItemList(),
535 | PCItemList : getPCItemList(),
536 | checksum : getChecksum(),
537 | money : getMoney(),
538 | currentPCBox : getCurrentPCBox(),
539 | seenList : getSeenList(),
540 | ownedList : getOwnedList(),
541 | playerPosition : getPlayerPosition(),
542 | pikachuFriendship: getPikachuFriendship(),
543 | partyList : getPartyList(),
544 | currentBoxList : getCurrentBoxList()
545 | };
546 | }
--------------------------------------------------------------------------------
/utlities/css/hex.css:
--------------------------------------------------------------------------------
1 |
2 | h1 {
3 | text-align: center;
4 | }
5 |
6 | #container {
7 | width: 800px;
8 | min-height: 100px;
9 | border: #000000 solid 1px;
10 | margin: 0 auto;
11 | }
12 |
13 | #container h2 {
14 | text-align: center;
15 | }
16 |
17 | .hex abbr {
18 | border-bottom-width: 0;
19 | }
20 |
21 |
22 | #inputSection {
23 | border: #000000 solid 1px;
24 | width : 300px;
25 | background: #ffffff;
26 | }
27 |
28 | @media (min-width: 1450px) {
29 | #inputSection {
30 | position: fixed;
31 | right : 1px;
32 | }
33 | }
34 |
35 | @media (max-width: 1450px) {
36 | #inputSection {
37 | margin: 0 auto;
38 | }
39 | }
40 |
41 |
42 | #inputSection h3 {
43 | margin-bottom: 0;
44 | margin-top: 0;
45 | text-align: center;
46 | }
47 |
48 | #inputSection label, #inputSection .label {
49 | font-weight: bold;
50 | }
51 |
52 | #noteBox {
53 | width: 90%;
54 | height: 100px;
55 | }
56 |
57 | .noted {
58 | background: green;
59 | }
60 |
61 | #gotoResult {
62 | color: red;
63 | font-weight: bold;
64 | margin-left: 10px;
65 | }
66 |
67 | #selectedHex {
68 | background: yellow;
69 | }
70 |
71 | #dataTable {
72 | text-align: center;
73 | margin: 0 auto;
74 | border: #000000 solid 1px;
75 | border-collapse:collapse;
76 | }
77 |
78 | #dataTable td {
79 | border: 1px solid #000000;
80 | }
81 |
--------------------------------------------------------------------------------
/utlities/hexViewer.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | File Hex Viewer
5 |
6 |
7 |
8 |
9 | Basic Hex Viewer
10 |
11 |
22 |
23 |
24 |
Please select a file to begin
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/utlities/js/hexViewer.js:
--------------------------------------------------------------------------------
1 |
2 | $(document).ready(function() {
3 | var $fileIn = $("#fileInput");
4 | var $gotoBox = $("#gotoBox");
5 | if(supportsFileAPI()) {
6 | $fileIn.bind("change", loadFile);
7 | $gotoBox.bind("keypress", searchTable);
8 | if(supportsStorage()) {
9 | $("#saveNotes").bind("click", saveNotes);
10 | $("#noteBox").bind("blur", setNote);
11 | $("#noteBox").bind("keypress", function(e) {
12 | if(e.keyCode === 13) {
13 | setNote(e);
14 | }
15 | });
16 | $("#clearNotes").bind("click", clearNotes);
17 | $(window).on('beforeunload', saveNotes); // save when they close or navigate away
18 | }
19 | else {
20 | $("#notesSection").remove();
21 | }
22 | }
23 | else {
24 | $("#container").html(
25 | "Your browser does not support the HTML5 File API
" +
26 | "This application makes use of the HTML5 File API to read local files.
" +
27 | "Please update your browser
"
28 | );
29 | $("#inputSection").remove();
30 | }
31 |
32 |
33 | });
34 |
35 | /**
36 | * Function that returns true if the user's web browser supports the HTML5 File API
37 | * @returns {boolean}
38 | */
39 | function supportsFileAPI() {
40 | return window.File && window.FileReader && window.FileList && window.Blob;
41 | }
42 |
43 | /**
44 | * Function to check if Storage API is supported
45 | * @returns {boolean}
46 | */
47 | function supportsStorage() {
48 | return window.localStorage != null && window.sessionStorage != null;
49 | }
50 |
51 | function loadFile(evt) {
52 | evt = evt.originalEvent || evt;
53 | var file = evt.target.files[0];
54 | if(file != null) {
55 | openFile(file);
56 | }
57 | }
58 |
59 | function openFile(file) {
60 | var reader = new FileReader();
61 | reader.onload = (function(theFile) {
62 | return function(e) {
63 | var container = document.getElementById("container");
64 | container.innerHTML = "";
65 | createDataTable(e.target.result, "Contents of " + theFile.name, container);
66 | if(supportsStorage()) {
67 | loadNotes(theFile.name);
68 | changeNote(null);
69 | }
70 | };
71 | })(file);
72 | reader.readAsBinaryString(file);
73 | }
74 |
75 | function int2HexString(i) {
76 | var str = i.toString(16);
77 | if(str.length < 2) {
78 | str = "0" + str;
79 | }
80 | return str.toUpperCase();
81 | }
82 |
83 | function createDataTable(data, heading, container) {
84 | var headingEl = document.createElement("h2");
85 | headingEl.id = "dataHeading";
86 | headingEl.appendChild(document.createTextNode(heading))
87 | container.appendChild(headingEl);
88 |
89 | var tableEl = document.createElement("table");
90 | tableEl.id = "dataTable";
91 | var topRow = document.createElement("tr");
92 | var topLeftTh = document.createElement("th");
93 | tableEl.appendChild(topRow);
94 | topRow.appendChild(topLeftTh);
95 | var columns = 16;
96 | for(var i = 0; i < columns; i ++) {
97 | var th = document.createElement("th");
98 | th.innerHTML = int2HexString(i);
99 | topRow.appendChild(th);
100 | }
101 |
102 | function getHex(offset) {
103 | return int2HexString(data.charCodeAt(offset));
104 | }
105 |
106 | var count = 0;
107 | while(count < data.length) {
108 | var tr = document.createElement("tr");
109 | var th = document.createElement("th");
110 | th.innerHTML = int2HexString(count);
111 | tr.appendChild(th);
112 | for(var i = 0;i < columns; i ++) {
113 | var td = document.createElement("td");
114 | td.className = "hex";
115 | td.setAttribute("pos", int2HexString(count));
116 | td.innerHTML = "" + getHex(count) + "";
117 | tr.appendChild(td);
118 | count ++;
119 | if(count >= data.length) break;
120 | }
121 | tableEl.appendChild(tr);
122 | }
123 |
124 | container.appendChild(tableEl);
125 |
126 | $(".hex").bind("click", function() {
127 | var $abbr = $(this).find("abbr");
128 | selectHex($abbr[0]);
129 | changeNote($abbr[0]);
130 | })
131 | }
132 |
133 | function selectHex(element) {
134 | var previous = document.getElementById("selectedHex");
135 | if(previous != null) {
136 | previous.id = "";
137 | }
138 | if(element != null) {
139 | element.id = "selectedHex";
140 | var gotoBox = document.getElementById("gotoBox");
141 | gotoBox.value = element.getAttribute("name");
142 | document.getElementById("gotoResult").innerHTML = "Found";
143 | }
144 | setDebugDiv(element);
145 | }
146 |
147 | function setDebugDiv(element) {
148 | var $debugDiv = $("#debugData");
149 | function hexString2ascii(hex) {
150 | var str = '';
151 | for (var i = 0; i < hex.length; i += 2)
152 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
153 | return str;
154 | }
155 | function hexString2int(hex) {
156 | var str = '';
157 | for (var i = 0; i < hex.length; i += 2)
158 | str += parseInt(hex.substr(i, 2), 16);
159 | return str;
160 | }
161 | function hexString2binary(hex) {
162 | return parseInt(hex, 16).toString(2);
163 | }
164 | if(element != null) {
165 | $debugDiv.html(
166 | "As Text: "+ hexString2ascii(element.innerHTML) +"
" +
167 | "As Integer: "+ hexString2int(element.innerHTML) +"
" +
168 | "As Binary: "+ hexString2binary(element.innerHTML) +"
"
169 | );
170 | }
171 | else {
172 | $debugDiv.html("");
173 | }
174 | }
175 |
176 | function searchTable(event) {
177 | if(event.keyCode === 13) {
178 | var previous = document.getElementById("selectedHex");
179 | if(previous != null) {
180 | previous.id = "";
181 | }
182 | var elements = document.getElementsByName(event.target.value);
183 | if(elements.length > 0) {
184 | elements[0].scrollIntoView(true);
185 | elements[0].id = "selectedHex";
186 | changeNote(elements[0]);
187 | document.getElementById("gotoResult").innerHTML = "Found";
188 | setDebugDiv(elements[0]);
189 | }
190 | else {
191 | document.getElementById("gotoResult").innerHTML = "Not Found";
192 | setDebugDiv(null);
193 | }
194 | }
195 | }
196 |
197 | function changeNote(element) {
198 | if(element != null) {
199 | $("#noteBox").val(element.getAttribute("note"));
200 | }
201 | else {
202 | $("#noteBox").val("");
203 | }
204 | }
205 |
206 | function setNote() {
207 | var fileInput = document.getElementById("fileInput");
208 | if(fileInput.files.length > 0) {
209 | var txt = document.getElementById("noteBox").value;
210 | var gotoBox = document.getElementById("gotoBox");
211 | var elementName = gotoBox.value;
212 | var $abbr = $(".hex abbr[name="+ elementName +"]");
213 | if(txt != null && txt.length > 0) {
214 | $abbr.attr("note", txt);
215 | $abbr.addClass("noted");
216 | }
217 | else {
218 | $abbr.removeAttr("note");
219 | $abbr.removeClass("noted");
220 | }
221 | }
222 | }
223 |
224 | function saveNotes() {
225 | var fileInput = document.getElementById("fileInput");
226 | if(fileInput.files.length > 0) {
227 | var filename = fileInput.files[0].name;
228 | var $abbrs = $(".hex abbr.noted");
229 | if($abbrs.size() > 0) {
230 | var notes = {};
231 | $abbrs.each(function() {
232 | var $abbr = $(this);
233 | if($abbr.attr("note") != null) {
234 | notes[$abbr.attr("name")] = $abbr.attr("note");
235 | }
236 | });
237 | console.log("Notes saved");
238 | localStorage.setItem(filename, JSON.stringify(notes));
239 | }
240 | else {
241 | localStorage.removeItem(filename);
242 | }
243 |
244 | }
245 | }
246 |
247 | function loadNotes(filename) {
248 | var notes = JSON.parse(localStorage.getItem(filename));
249 | if(notes != null) {
250 | for(var key in notes) {
251 | var $abbr = $(".hex abbr[name="+ key +"]");
252 | $abbr.attr("note", notes[key]);
253 | $abbr.addClass("noted");
254 | }
255 | console.log("Notes loaded");
256 | }
257 | }
258 |
259 | function clearNotes() {
260 | var fileInput = document.getElementById("fileInput");
261 | if(fileInput.files.length > 0) {
262 | var filename = fileInput.files[0].name;
263 | if(confirm("Continuing will delete all notes for " +filename) == false) {
264 | return;
265 | }
266 | localStorage.removeItem(filename);
267 | $(".hex abbr.noted").removeAttr("note");
268 | $(".hex abbr.noted").removeClass("noted");
269 | changeNote(null);
270 | }
271 | }
--------------------------------------------------------------------------------