diff options
-rw-r--r-- | CHANGELOG.txt | 12 | ||||
-rw-r--r-- | PrivateAccess.cpp | 31 | ||||
-rw-r--r-- | README.txt | 64 | ||||
-rwxr-xr-x | SE.o | bin | 0 -> 45824 bytes | |||
-rw-r--r-- | SavEdit.cpp | 157 | ||||
-rw-r--r-- | SavEdit.h | 37 | ||||
-rw-r--r-- | TextOptions.cpp | 68 | ||||
-rw-r--r-- | UserOptions.cpp | 194 | ||||
-rw-r--r-- | compile.sh | 1 | ||||
-rw-r--r-- | main.cpp | 51 | ||||
-rw-r--r-- | notes.txt | 30 |
11 files changed, 645 insertions, 0 deletions
diff --git a/CHANGELOG.txt b/CHANGELOG.txt new file mode 100644 index 0000000..c516ca3 --- /dev/null +++ b/CHANGELOG.txt @@ -0,0 +1,12 @@ +# April 26, 2023 + +* Created the changelog file, knowing that I should have been using git. +* Added checksum subroutine +* Add text-table conversion +* I changed vector "offset" to "mem". It made no sense to call it "offset" when + it was the set that is mem contains offsets. +* Tested modMem subroutine (changed the first char in OT name to "Z".): It + worked. +* Tested the save feature. It works. +* Finally got the money edit to work. +* Probably going to push this up in a few moments. diff --git a/PrivateAccess.cpp b/PrivateAccess.cpp new file mode 100644 index 0000000..36a90ed --- /dev/null +++ b/PrivateAccess.cpp @@ -0,0 +1,31 @@ +#include <iostream> + +#include "SavEdit.h" + +SavEdit::SavEdit() +{ + /* + * init + */ + + filename = "Pokemon - Red Version (USA, Europe) (SGB Enhanced).sav"; + lang = en_US; +} + +std::string SavEdit::getFilename() +{ + /* + * Return the file name. + */ + + return filename; +} + +void SavEdit::setFilename(std::string fname) +{ + /* + * Set the file name. + */ + + filename = fname; +} diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..43090e0 --- /dev/null +++ b/README.txt @@ -0,0 +1,64 @@ +# Pokemon Sav Editor (PSE) +## Mionathraìm SAV + +This is a command-line, GNU/Linux supported Pokemon SAV editor. + +It currently only supports Pokemon Red and Blue. + +I was going to go with my normal naming theme, but thought "Mionathraìm SAV" +might be too hard to type/pronounce.. So, it'll be "Pokemon Sav Editor (PSE)" +and in the sub-title we'll call it "Mionathraìm SAV". + +## Why a pokemon sav editor? + +PKHeX doesn't work on Linux. This does. + +## Release + +I did compile it using `g++ (Gentoo 12.2.1_p20230304 p13) 12.2.1 20230304` +so if that will work fine for you, go for it. + + +## What about other generations? + +This is just a starting point. Feel free to help though. + + +## What about [thing you want] + +Request it, give me the offset addr, or add it in yourself. This is just +something I'm doing. + + +## How to run + +``` +$ sh compile.sh +$ ./SE.o [FILENAME].sav + +``` + +## Contribute + +Make a PR. + +## Where are you? + +* I'm required to use 2FA apparently so if I'm not back up, that's why. I'll see +about fixing that when I can. + +* I'm also working on two degrees right now. (Mathematics and Computer Science) + +* I'll try to be around when I can. + +## Goal + +The goal of this project is to basically create a PKHeX replacement for Linux. +I want to go all the way up to modern generations, if possible. + + +## Nice idea + +Language support for: ie_gle, ja_jp + + Binary files differdiff --git a/SavEdit.cpp b/SavEdit.cpp new file mode 100644 index 0000000..b8996ab --- /dev/null +++ b/SavEdit.cpp @@ -0,0 +1,157 @@ +#include <iostream> +#include <fstream> +#include <vector> + +#include "SavEdit.h" + +#define SAV_LEN 32767 + + +void SavEdit::open() +{ + /* + * Store save file in a vector. + */ + + std::ifstream fs(filename, std::ios::in | std::ios::binary); + + // Verify file is opened + if(!fs) + { + // TODO: translation + subroutine needed. + std::cout << "Cannot open file." << std::endl; + exit(1); + } + + // Iterate through file, adding to vector. + for(int i = 0; i <= SAV_LEN; ++i) + mem.push_back(fs.get()); +} + +void SavEdit::read() +{ + /* + * Iterates through the opened file. + * This is mostly just to make sure it + * properly loaded everything up. I will + * probably be modifying this for editing + * later. + */ + + for(int i = 0; i <= SAV_LEN; ++i) + { + // offset address + std::cout.fill('0'); + std::cout << "0x"; + std::cout.width(4); + std::cout << std::hex << i; + std::cout << ":\t"; + + // print hex value + std::cout.width(2); + std::cout << std::hex << mem.at(i) << std::endl; + } + +} + +void SavEdit::save(std::string fname) +{ + /* + * This subroutine writes the binary + * file using the offset vector. + */ + + std::ofstream bfs(fname, std::ios::out | std::ios::binary); + + if(!bfs) + { + // TODO: Translate + subroutine needed. + std::cout << "Cannot create file." << std::endl; + exit(2); + } + + for(int i = 0; i <= SAV_LEN; ++i) + bfs.write( (char*)&mem.at(i), 1); + +} + +void SavEdit::backup() +{ + /* + * Create the backup of the sav file + * before editing. + */ + + save(getFilename() + ".BAK"); +} + +int SavEdit::checksum() +{ + /* + * Set the checksum for the new SAV + */ + + // Autodetect if JP later. + bool JP = false; + + // The Japanese gen 1 has a different offset + int checksum_offset = JP ? 0x3594 : 0x3523; + + // Starting point for Bank One + int bankStart = 0x2598; + + // Our new checksum + unsigned int check_sum = 0; + + // Take all the data from bank 1, but skip the checksum. + // (Non-JP: 0x2000 to 0x3522) + for(int i = bankStart; i < checksum_offset; ++i) + check_sum += mem.at(i); + + // bitwise NOT + check_sum = ~check_sum; + + // bitwise AND_EQUAL + check_sum &= 0xff; + + // Update the checksum. + mem.at(checksum_offset) = check_sum; + + return check_sum; +} + +int SavEdit::convert(char given) +{ + /* + * Converts the given character to the proper value + * in the text table. + */ + + // The characters are 63 over their ASCII value. + int mod = 63; + + mod += (int)given; + + return mod; +} + +void SavEdit::modMem(int offset, char val) +{ + /* + * Edits the specified offset to the given value, + * after converting the given value to it's proper + * format. + */ + + // note: should this subroutine contain checks? + mem.at(offset) = convert(val); +} + +void SavEdit::saveNewFile() +{ + /* + * This updates the new file and saves it. + */ + + save(getFilename()); +} diff --git a/SavEdit.h b/SavEdit.h new file mode 100644 index 0000000..699cb81 --- /dev/null +++ b/SavEdit.h @@ -0,0 +1,37 @@ +#ifndef SAVEDIT_H +#define SAVEDIT_H + +#include <iostream> +#include <vector> + +class SavEdit +{ + public: + SavEdit(); + void open(); + void read(); + void save(std::string fname); + std::string getFilename(); + void setFilename(std::string fname); + void backup(); + int checksum(); + int convert(char given); + void modMem(int offset, char val); + void saveNewFile(); + void setLang(int i); + void greeting(); + void languageForm(); + void textOT(); + void modify(const int BEGIN, const int END); + void cashModify(); + bool select(); + void clear(); + + private: + std::string filename; + std::vector<int> mem; + enum language{en_US=0, ie_gle=1, ja_jp=2}; + int lang; +}; + +#endif diff --git a/TextOptions.cpp b/TextOptions.cpp new file mode 100644 index 0000000..e1ce711 --- /dev/null +++ b/TextOptions.cpp @@ -0,0 +1,68 @@ +#include <iostream> + +#include "SavEdit.h" + +void SavEdit::setLang(int i) +{ + /* + * Set the language. + */ + lang = i; +} + +void SavEdit::greeting() +{ + /* + * Greet the user in their language of choice. + */ + switch(lang) + { + case en_US: + std::cout << "Hello." << std::endl; + break; + case ie_gle: + std::cout << "dia duit." << std::endl; + break; + case ja_jp: + std::cout << "こんにちは" << std::endl; + break; + default: + std::cout << "Something went wrong." << std::endl; + break; + } +} +void SavEdit::languageForm() +{ + /* + * Allow the user to switch to their preferred language. + */ + int selection = 0; + + std::cout << "Language selection:\n"; + std::cout << "\t0. American English.\n"; + std::cout << "\t1. Gaelige\n"; + std::cout << "\t2. 日本語\n"; + std::cout << std::endl; + + std::cout << "Language: "; + std::cin >> selection; + + setLang(selection); + +} + +void SavEdit::textOT() +{ + /* + * Template subroutine to add language support. + */ + switch(lang) + { + case en_US: + case ie_gle: + case ja_jp: + default: + std::cout << "OT: "; + break; + } +} diff --git a/UserOptions.cpp b/UserOptions.cpp new file mode 100644 index 0000000..08768b8 --- /dev/null +++ b/UserOptions.cpp @@ -0,0 +1,194 @@ +#include <iostream> +#include <cstring> + +#include "SavEdit.h" + +void SavEdit::clear() +{ + /* + * Weird screen clear trick I do not remember where I learned it. + */ + std::cout << "\033[2J\033[1;1H"; +} + +void SavEdit::modify(const int BEGIN, const int END) +{ + /* + * Reusable code for editing sections goes here. + */ + + // Make sure the input stays within its expected limits. + bool correct = false; + + // +1 to include self. + const int SIZE_LIMIT = (END - BEGIN) + 1; + + // This is the user input string. + std::string s; + + // Prevent the user from breaking limits. + while(!correct) + { + // TODO: Clear / Redraw screen + std::cout << "input: "; + + std::cin >> s; + + if((const int)s.length() <= SIZE_LIMIT) + correct = true; + + // Clear the buffer. + std::cin.clear(); + } + + for(unsigned long int i = 0; i <= (unsigned long int)(END - BEGIN); ++i) + { + if(i < s.length()) + { + // Apply one char at a time. + const char *c = s.substr(i, 1).c_str(); + modMem( (BEGIN + i), c[0]); + } + + // Some require 0x50 at the end (TRAINER + RIVAL NAME). + if(i == s.length()) + if(BEGIN == 0x2598 || BEGIN == 0x25F6) + mem.at(BEGIN + i) = 0x50; + } + +} + +void SavEdit::cashModify() +{ + /* + * Money is handled differently. + */ + + // Check for correctness + bool correct = false; + + // User input + std::string s; + + while(!correct) + { + std::cout << "input (numbers only): " << std::endl; + + std::cin >> s; + + if(atoi(s.c_str()) <= 999999) + { + correct = true; + } + + // clear the buffer. + std::cin.clear(); + } + + // Pad with zeros + s = std::string(6 - s.length(), '0') + s; + + // Apply the values + for(int i = 0; i <= 2; ++i) + { + // Convert to char + const char *c = s.substr((i*2), 2).c_str(); + // Convert to int (the char values are in the way.) + int x_val = ((c[0] * 10) + c[1]); + // Remove 528 because, for some reason, that's how much higher it is. + int x_tot = x_val - 528; + + // BUGFIX: For some reason, we need to remove 1 extra for 1 and 2? + if(c[0] == 0) + if((c[1] == '1') || (c[1] == '2')) + x_tot--; + + // apply + mem.at(0x25f3 + i) = x_tot; + } +} + +bool SavEdit::select() +{ + /* + * A giant switch for determining which offsets to edit. + * + * TODO: May want to break this down into something else. + */ + + // For the loop. I might not keep it like this. + bool finished = false; + + // Check for correct input. + bool correct = false; + int OPTIONS_LIMIT = 4; + + // user input. + int selection; + + // Check if supported. + while(!correct) + { + // TODO: translate + create subroutine + // TODO: Clear / Redraw Screen + // TODO: Create a better menu + std::cout << "EDIT: " << std::endl; + std::cout << "\t1. Player Name" << std::endl; + std::cout << "\t2. RIVAL Name" << std::endl; + std::cout << "\t3. MONEY" << std::endl; + std::cout << "\t4. QUIT, DO NOT SAVE" << std::endl; + std::cout << "\t0. SAVE AND QUIT" << std::endl; + std::cout << "YOUR INPUT: "; + + std::cin >> selection; + + if(selection >= 0) + if(selection <= OPTIONS_LIMIT) + correct = true; + + + // clear the buffer. + std::cin.clear(); + } + + + // TODO: Allow for custom edits? + + switch(selection) + { + case 0: + std::cout << "SAVING FILE..." << std::endl; + checksum(); + saveNewFile(); + finished = true; + break; + case 1: + std::cout << "EDITING TRAINER NAME:" << std::endl; + modify(0x2598, 0x259E); + break; + case 2: + std::cout << "EDITING RIVAL NAME:" << std::endl; + modify(0x25F6, 0x25FD); + break; + case 3: + std::cout << "EDITING MONEY:" << std::endl; + //std::cout << "SETTING TO 999,999 POKEDOLLARS." << std::endl; + //mem.at(0x25F3) = 99; + //mem.at(0x25F4) = 99; + //mem.at(0x25F5) = 99; + cashModify(); + //modify(0x25F3, 0x25F5); + break; + case 4: + std::cout << "Quitting without saving." << std::endl; + finished = true; + break; + default: + std::cout << "NOT AN OPTION" << std::endl; + break; + } + + // TODO: Display what is currently there + + return finished; +} diff --git a/compile.sh b/compile.sh new file mode 100644 index 0000000..9982efa --- /dev/null +++ b/compile.sh @@ -0,0 +1 @@ +g++ -Wall main.cpp SavEdit.cpp PrivateAccess.cpp UserOptions.cpp TextOptions.cpp -o SE.o diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..eaca369 --- /dev/null +++ b/main.cpp @@ -0,0 +1,51 @@ +#include <iostream> + +#include "SavEdit.h" + +int main(int argc, char *argv[]) +{ + // Create obj + SavEdit save; + + if(argc == 2) + { + // Set the file name + save.setFilename(argv[1]); + } + + + // Select language. + //save.languageForm(); + + // Send greeting. + //save.greeting(); + + // store file in vector + save.open(); + + // print vector + //save.read(); + + // create the backup + save.backup(); + + // Test OT + //save.modify(0x2598, 0x259E); + + // TEST OPTIONS + bool done = false; + while(!done) + done = save.select(); + + + // Test the rival. + //save.modify(0x25F6, 0x25FC); + + // Update the checksum. + //save.checksum(); + + // Save the new file + //save.saveNewFile(); + + return 0; +} diff --git a/notes.txt b/notes.txt new file mode 100644 index 0000000..26d9ed9 --- /dev/null +++ b/notes.txt @@ -0,0 +1,30 @@ +Stuff to keep in mind. + +## +# Random note +## + +I have found three offset points require an ending with 0x50: +0x259E, 0x25FD, 2C5E + +0x259E is the trainer, 0x25FD is the rival, but I am uncertain what 2C5E is at +this time. + + +## +# Specific offset points +## + +# TRAINER NAME: +OFFSET: 0x2598 - 0x259E +NOTE: It must end with 0x50 + +# RIVAL NAME: +OFFSET: 0x25F6 - 0x25FD +NOTE: It must end with 0x50 + +# MONEY: +OFFSET: 0x25F3 - 0x25F5 +NOTE: Reads hex weird. Only accepts 0-9. Basically, do not convert with money. + + |