Przeglądaj źródła

The whole bombing mission is now playable without editing the original data!

- Bit operations implemented in Lua scripts.
- Fixed opcodes BITON, BITOFF, GOLDU, GOLDD, CHGLD, CKITM.
- Music doesn't reset on map change.
- 15th data bank is now saved too.
- Deleted original music from the repo.
Iñigo Valentin 3 lat temu
rodzic
commit
026c97614a
28 zmienionych plików z 153 dodań i 36 usunięć
  1. 1 1
      CMakeLists.txt
  2. 4 1
      README.md
  3. 25 7
      V-Gears-Installer/src/FieldDataInstaller.cpp
  4. 2 2
      V-Gears-Installer/src/decompiler/field/FieldDisassembler.cpp
  5. 1 0
      V-Gears-Installer/src/decompiler/field/instruction/FieldBackgroundInstruction.cpp
  6. 1 0
      V-Gears-Installer/src/decompiler/field/instruction/FieldCameraInstruction.cpp
  7. 2 2
      V-Gears-Installer/src/decompiler/field/instruction/FieldCondJumpInstruction.cpp
  8. 3 2
      V-Gears-Installer/src/decompiler/field/instruction/FieldMathInstruction.cpp
  9. 1 0
      V-Gears-Installer/src/decompiler/field/instruction/FieldMediaInstruction.cpp
  10. 1 0
      V-Gears-Installer/src/decompiler/field/instruction/FieldModelInstruction.cpp
  11. 1 0
      V-Gears-Installer/src/decompiler/field/instruction/FieldModuleInstruction.cpp
  12. 3 2
      V-Gears-Installer/src/decompiler/field/instruction/FieldPartyInstruction.cpp
  13. 1 0
      V-Gears-Installer/src/decompiler/field/instruction/FieldWalkmeshInstruction.cpp
  14. 1 3
      V-Gears-Installer/src/decompiler/field/instruction/FieldWindowInstruction.cpp
  15. 3 3
      V-Gears/include/Version.h
  16. 5 0
      V-Gears/include/core/AudioManager.h
  17. 2 2
      V-Gears/include/core/Savemap.h
  18. 8 0
      V-Gears/src/core/AudioManager.cpp
  19. BIN
      data/data/musics/ffvii_main_theme.mp3
  20. BIN
      data/data/musics/ffvii_main_theme.ogg
  21. BIN
      data/data/musics/loop1.ogg
  22. BIN
      data/data/musics/loop2.ogg
  23. BIN
      data/data/musics/loop3.ogg
  24. 1 1
      data/data/screens/main_menu/prototypes.xml
  25. 9 4
      data/data/scripts/data.lua
  26. 2 0
      data/data/scripts/menu/name_menu.lua
  27. 4 4
      data/data/scripts/save.lua
  28. 72 2
      data/data/scripts/system.lua

+ 1 - 1
CMakeLists.txt

@@ -14,7 +14,7 @@ project(V-Gears)
 set(CMAKE_PACKAGE_ICON v-gears.png)
 set(VGears_VERSION__MAJOR 0)
 set(VGears_VERSION__MINOR 1)
-set(VGears_VERSION__PATCH 0)
+set(VGears_VERSION__PATCH 1)
 set(VGears_VERSION ${VGears_VERSION__MAJOR}.${VGears_VERSION__MINOR}.${VGears_VERSION__PATCH})
 
 # Generate version header.

+ 4 - 1
README.md

@@ -59,7 +59,10 @@ Well, let's call it a work in progress... of a work in progress.
 
 
 If the question was "How deep into the game can I play?", then the answer is
-> You can ALMOST play the whole Mako Reactor 1 mission. But dumbass Biggs won't show up to open the way out, so you die on fire. Or you would, if the bomb timer actually worked.
+
+[<img src="https://v-gears.inigovalentin.com/img/screenshot/max_progress.png" alt="Max progress" width="450"/>](https://v-gears.inigovalentin.com/img/screenshot/max_progress.png "Max progress")
+
+Until there. Whitout cheats, and without using the developer console, the game hangs there.
 
 But don't worry! The engine is not fixed sequentially, or map by map. Once a fix is implememnted, it applies to all maps at once, anb big chunck of the game are unlocked after each fix.
 

+ 25 - 7
V-Gears-Installer/src/FieldDataInstaller.cpp

@@ -104,21 +104,39 @@ bool FieldDataInstaller::IsTestField(const Ogre::String& resource_name){
       || resource_name == "md1stin"
       || resource_name == "md1_1"
       || resource_name == "md1_2"
-      || resource_name == "md8_1"
+      || resource_name == "nrthmk"
       || resource_name == "nmkin_1"
+      || resource_name == "elevtr1"
       || resource_name == "nmkin_2"
       || resource_name == "nmkin_3"
       || resource_name == "nmkin_4"
       || resource_name == "nmkin_5"
-      || resource_name == "nrthmk"
-      || resource_name == "elevtr1"
+      || resource_name == "southmk1"
+      || resource_name == "southmk2"
+      || resource_name == "smkin_1"
+      || resource_name == "smkin_2"
+      || resource_name == "smkin_3"
+      || resource_name == "smkin_4"
+      || resource_name == "smkin_5"
+      || resource_name == "md8_1"
+      || resource_name == "md8_2"
+      || resource_name == "md8_3"
+      || resource_name == "md8_4"
+      || resource_name == "md8brdg"
+      || resource_name == "cargoin"
       || resource_name == "tin_1"
       || resource_name == "tin_2"
+      || resource_name == "tin_3"
+      || resource_name == "tin_4"
       || resource_name == "rootmap"
-      || resource_name == "md8_1"
-      //|| resource_name == "md8_2"
-      //|| resource_name == "md8_3"
-      //|| resource_name == "md8_4"
+      || resource_name == "mds7st1"
+      || resource_name == "mds7st2"
+      || resource_name == "mds7st3"
+      || resource_name == "mds7st32"
+      || resource_name == "mds7_w1"
+      || resource_name == "mds7_w2"
+      || resource_name == "mds7_w3"
+      || resource_name == "mds7"
     ){return true;}
     return false;
 }

+ 2 - 2
V-Gears-Installer/src/decompiler/field/FieldDisassembler.cpp

@@ -684,10 +684,10 @@ bool FieldDisassembler::ReadOpCodesToPositionOrReturn(
             case OPCODES::GOLDU:
                 // Goldu can have two argument formats: (nybble, word) or (nybble, byte, 3 bytes)
                 // Read it as nybble, byte, byte, byte, byte and operate later
-                ParseOpcode(full_opcode, "GOLDU", new FieldPartyInstruction(), 0, "BBBBB");
+                ParseOpcode(full_opcode, "GOLDU", new FieldPartyInstruction(), 0, "NBBBB");
                 break;
             case OPCODES::GOLDD:
-                ParseOpcode(full_opcode, "GOLDD", new FieldPartyInstruction(), 0, "Nww");
+                ParseOpcode(full_opcode, "GOLDD", new FieldPartyInstruction(), 0, "NBBBB");
                 break;
             case OPCODES::CHGLD:
                 ParseOpcode(full_opcode, "CHGLD", new FieldPartyInstruction(), 0, "NBB");

+ 1 - 0
V-Gears-Installer/src/decompiler/field/instruction/FieldBackgroundInstruction.cpp

@@ -28,6 +28,7 @@ void FieldBackgroundInstruction::ProcessInst(
   Function& func, ValueStack&, Engine* engine, CodeGenerator *code_gen
 ){
     FunctionMetaData md(func.metadata);
+    //std::cout << "Background opcode: " << opcode_ << "\n";
     switch (opcode_){
         case OPCODES::BGPDH: code_gen->WriteTodo(md.GetEntityName(), "BGPDH");break;
         case OPCODES::BGSCR: code_gen->WriteTodo(md.GetEntityName(), "BGSCR"); break;

+ 1 - 0
V-Gears-Installer/src/decompiler/field/instruction/FieldCameraInstruction.cpp

@@ -28,6 +28,7 @@ void FieldCameraInstruction::ProcessInst(
   Function& func, ValueStack&, Engine* engine, CodeGenerator *code_gen
 ){
     FunctionMetaData md(func.metadata);
+    //std::cout << "Camera opcode: " << opcode_ << "\n";
     switch (opcode_){
     case OPCODES::NFADE: ProcessNFADE(code_gen); break;
     case OPCODES::SHAKE: code_gen->WriteTodo(md.GetEntityName(), "SHAKE"); break;

+ 2 - 2
V-Gears-Installer/src/decompiler/field/instruction/FieldCondJumpInstruction.cpp

@@ -74,7 +74,7 @@ void FieldCondJumpInstruction::ProcessInst(
         case 8: op = "|"; break;
         case 9:
             {
-                op = "bit(" + params_[0]->GetString() + ", " + params_[2]->GetString()
+                op = "get_bank_bit(" + params_[0]->GetString() + ", " + params_[2]->GetString()
                   + ", " + destination + ") == 1";
                 ValuePtr v = new UnquotedStringValue(op);
                 stack.Push(v);
@@ -82,7 +82,7 @@ void FieldCondJumpInstruction::ProcessInst(
             return;
         case 0xA:
             {
-                op = "bit(" + params_[0]->GetString() + ", " + params_[2]->GetString()
+                op = "get_bank_bit(" + params_[0]->GetString() + ", " + params_[2]->GetString()
                   + ", " + destination + ") == 0";
                 ValuePtr v = new UnquotedStringValue(op);
                 stack.Push(v);

+ 3 - 2
V-Gears-Installer/src/decompiler/field/instruction/FieldMathInstruction.cpp

@@ -29,6 +29,7 @@ void FieldMathInstruction::ProcessInst(
 ){
     FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
     FunctionMetaData md(func.metadata);
+    //std::cout << "Math opcode: " << opcode_ << "\n";
     switch (opcode_){
         case OPCODES::PLUS_: ProcessSaturatedPLUS(code_gen); break;
         case OPCODES::PLUS2_: ProcessSaturatedPLUS2(code_gen); break;
@@ -223,13 +224,13 @@ void FieldMathInstruction::ProcessSETBYTE_SETWORD(CodeGenerator* code_gen){
 }
 
 void FieldMathInstruction::ProcessBITON(CodeGenerator* code_gen){
-    code_gen->AddOutputLine((boost::format("bit_on(%1%, %2%, %3%)")
+    code_gen->AddOutputLine((boost::format("set_bank_bit(%1%, %2%, %3%, 1)")
       % params_[0]->GetUnsigned() % params_[2]->GetUnsigned() % params_[3]->GetUnsigned()
     ).str());
 }
 
 void FieldMathInstruction::ProcessBITOFF(CodeGenerator* code_gen){
-    code_gen->AddOutputLine((boost::format("bit_off(%1%, %2%, %3%)")
+    code_gen->AddOutputLine((boost::format("set_bank_bit(%1%, %2%, %3%, 0)")
       % params_[0]->GetUnsigned() % params_[2]->GetUnsigned() % params_[3]->GetUnsigned()
     ).str());
 }

+ 1 - 0
V-Gears-Installer/src/decompiler/field/instruction/FieldMediaInstruction.cpp

@@ -28,6 +28,7 @@ void FieldMediaInstruction::ProcessInst(
   Function& func, ValueStack&, Engine* engine, CodeGenerator *code_gen
 ){
     FunctionMetaData md(func.metadata);
+    //std::cout << "Media opcode: " << opcode_ << "\n";
     switch (opcode_){
         case OPCODES::BGMOVIE: code_gen->WriteTodo(md.GetEntityName(), "BGMOVIE"); break;
         case OPCODES::AKAO2: ProcessAKAO(code_gen); break;

+ 1 - 0
V-Gears-Installer/src/decompiler/field/instruction/FieldModelInstruction.cpp

@@ -29,6 +29,7 @@ void FieldModelInstruction::ProcessInst(
 ){
     FieldEngine& eng = static_cast<FieldEngine&>(*engine);
     FunctionMetaData md(func.metadata);
+    //std::cout << "Model opcode: " << opcode_ << "\n";
     switch (opcode_){
         case OPCODES::JOIN: ProcessJOIN(code_gen); break;
         case OPCODES::SPLIT: ProcessSPLIT(code_gen); break;

+ 1 - 0
V-Gears-Installer/src/decompiler/field/instruction/FieldModuleInstruction.cpp

@@ -28,6 +28,7 @@ void FieldModuleInstruction::ProcessInst(
   Function& func, ValueStack&, Engine* engine, CodeGenerator *code_gen
 ){
     FunctionMetaData md(func.metadata);
+    //std::cout << "Module opcode: " << opcode_ << "\n";
     switch (opcode_){
         case OPCODES::DSKCG: code_gen->WriteTodo(md.GetEntityName(), "DSKCG"); break;
         case (OPCODES::SPECIAL << 8) | OPCODES_SPECIAL::ARROW:

+ 3 - 2
V-Gears-Installer/src/decompiler/field/instruction/FieldPartyInstruction.cpp

@@ -28,6 +28,7 @@ void FieldPartyInstruction::ProcessInst(
   Function& func, ValueStack&, Engine* engine, CodeGenerator *code_gen
 ){
     FunctionMetaData md(func.metadata);
+    //std::cout << "Party opcode: " << opcode_ << "\n";
     switch (opcode_){
         case OPCODES::SPTYE: code_gen->WriteTodo(md.GetEntityName(), "SPTYE"); break;
         case OPCODES::GTPYE: code_gen->WriteTodo(md.GetEntityName(), "GTPYE"); break;
@@ -124,7 +125,7 @@ void FieldPartyInstruction::ProcessDLITM(CodeGenerator* code_gen){
 
 void FieldPartyInstruction::ProcessCKITM(CodeGenerator* code_gen){
     code_gen->AddOutputLine((
-      boost::format("Banks[%1][%2] = Inventory.get_item_quantity(%3%)")
+      boost::format("Banks[%1%][%2%] = Inventory.get_item_quantity(%3%)")
       % params_[0]->GetUnsigned() % params_[2]->GetUnsigned() % params_[1]->GetUnsigned()
     ).str());
 }
@@ -142,7 +143,7 @@ void FieldPartyInstruction::ProcessPRTYE(CodeGenerator* code_gen){
 
 void FieldPartyInstruction::ProcessCHGLD(CodeGenerator* code_gen){
     code_gen->AddOutputLine((
-      boost::format("Inventory.money_to_banks(%1%, %2%, %3, %4)")
+      boost::format("Inventory.money_to_banks(%1%, %2%, %3%, %4%)")
         % params_[0]->GetUnsigned() % params_[1]->GetUnsigned()
         % params_[2]->GetUnsigned() % params_[3]->GetUnsigned()
     ).str());

+ 1 - 0
V-Gears-Installer/src/decompiler/field/instruction/FieldWalkmeshInstruction.cpp

@@ -28,6 +28,7 @@ void FieldWalkmeshInstruction::ProcessInst(
   Function& func, ValueStack&, Engine* engine, CodeGenerator *code_gen
 ){
     FunctionMetaData md(func.metadata);
+    //std::cout << "WMesh opcode: " << opcode_ << "\n";
     switch (opcode_){
         case OPCODES::SLIP: code_gen->WriteTodo(md.GetEntityName(), "SLIP"); break;
         case OPCODES::UC: ProcessUC(code_gen); break;

+ 1 - 3
V-Gears-Installer/src/decompiler/field/instruction/FieldWindowInstruction.cpp

@@ -29,6 +29,7 @@ void FieldWindowInstruction::ProcessInst(
 ){
     FieldEngine& eng = static_cast<FieldEngine&>(*engine);
     FunctionMetaData md(func.metadata);
+    //std::cout << "Window opcode: " << opcode_ << "\n";
     switch (opcode_){
         case OPCODES::TUTOR: code_gen->WriteTodo(md.GetEntityName(), "TUTOR"); break;
         case OPCODES::WCLS: code_gen->WriteTodo(md.GetEntityName(), "WCLS"); break;
@@ -161,9 +162,6 @@ void FieldWindowInstruction::ProcessMENU(CodeGenerator* code_gen){
 }
 
 void FieldWindowInstruction::ProcessSTTIM(CodeGenerator* code_gen){
-    std::cout << "STTIM params:\n";
-    for (int i = 0; i < params_.size(); i ++) std::cout << "    " << params_[i] << "\n";
-    std::cout << "STTIM params END\n";
     FieldCodeGenerator* cg = static_cast<FieldCodeGenerator*>(code_gen);
     int h = std::stoi(FieldCodeGenerator::FormatValueOrVariable(
       cg->GetFormatter(), params_[0]->GetUnsigned(), params_[4]->GetSigned(),

+ 3 - 3
V-Gears/include/Version.h

@@ -22,8 +22,8 @@
 /**
  * Application version.
  */
-#define VGEARS_VERSION "0.1.0"
+#define VGEARS_VERSION "0.1.1"
 #define VGEARS_VERSION_MAJOR "0"
 #define VGEARS_VERSION_MINOR "1"
-#define VGEARS_VERSION_PATCH "0"
-#define VGEARS_VERSION_SIGNATURE "V-Gears v0.1.0"
+#define VGEARS_VERSION_PATCH "1"
+#define VGEARS_VERSION_SIGNATURE "V-Gears v0.1.1"

+ 5 - 0
V-Gears/include/core/AudioManager.h

@@ -295,6 +295,11 @@ class AudioManager : public Ogre::Singleton<AudioManager>{
                  */
                 float loop_;
 
+                /**
+                 * Name of the file currently being played.
+                 */
+                Ogre::String file_;
+
                 /**
                  * File played or to play.
                  */

+ 2 - 2
V-Gears/include/core/Savemap.h

@@ -931,12 +931,12 @@ class Savemap{
         /**
          * Number of data banks.
          */
-        static const unsigned int BANK_COUNT = 15;
+        static const unsigned int BANK_COUNT = 16;
 
         /**
          * Number of addresses in each data bank.
          */
-        static const unsigned int BANK_ADDRESS_COUNT = 254;
+        static const unsigned int BANK_ADDRESS_COUNT = 256;
 
         /**
          * MAx colour component value.

+ 8 - 0
V-Gears/src/core/AudioManager.cpp

@@ -231,11 +231,18 @@ void AudioManager::Player::Pause(){
 
 void AudioManager::Player::Play(const Ogre::String &file){
     boost::recursive_mutex::scoped_lock lock(*update_mutex_);
+
+
+    // If the same track is already playing, don't restart.
+
+    if (file_ == file) return;
+
     // Open vorbis file.
     if (ov_fopen(const_cast<char*>(file.c_str()), &vorbis_file_)){
         LOG_ERROR("Can't play file \"" + file + "\".");
         return;
     }
+    file_ = file;
     // Get file info.
     vorbis_info_ = ov_info(&vorbis_file_, -1.0f);
     // Create sound source
@@ -284,6 +291,7 @@ void AudioManager::Player::Stop(){
     // Cleanup
     ov_clear(&vorbis_file_);
     stream_finished_ = false;
+    file_ = "";
 }
 
 void AudioManager::Player::SetLoop(const float loop){

BIN
data/data/musics/ffvii_main_theme.mp3


BIN
data/data/musics/ffvii_main_theme.ogg


BIN
data/data/musics/loop1.ogg


BIN
data/data/musics/loop2.ogg


BIN
data/data/musics/loop3.ogg


+ 1 - 1
data/data/screens/main_menu/prototypes.xml

@@ -2,7 +2,7 @@
     <prototype name="MenuCharacterData">
         <text_area name="Name" x="0" y="0" font="FFVIIFont" visible="true" />
         <text_area name="Lv" x="0" y="5" text_name="CharacterDataLv" colour="0 0.9 0.9" font="FFVIIMenuFont" visible="true" />
-        <text_area name="LvNumber" x="18" y="13" font="FFVIITimerFont" visible="true" />
+        <text_area name="LvNumber" x="18" y="13" font="FFVIIMenuDigits" visible="true" />
         <text_area name="Hp" x="0" y="16" text_name="CharacterDataHp" colour="0 0.9 0.9" font="FFVIIMenuFont" visible="true" />
         <text_area name="HpCurrent" x="16" y="23" colour="0.9 0.9 0.9" font="FFVIIMenuDigits" visible="true" />
         <sprite name="HpSlash" image="images/icons/slash.png" x="51" y="23" width="4" height="8" visible="true" />

+ 9 - 4
data/data/scripts/data.lua

@@ -233,6 +233,7 @@ MenuSettings = {
 
 --- Current party.
 Party = {
+    MAX = 3,
     [1] = nil,
     [2] = nil,
     [3] = nil
@@ -244,9 +245,11 @@ Party = {
 -- 15 banks (indexes 0-14) with 255 addresses (indexes 0-254) each. Memory banks 4, 5, 7, 8 and 9
 -- are temporary and are not saved to the savemap.
 Banks = {}
-for i = 0, 14 do
+Banks.COUNT = 15
+Banks.SIZE = 255
+for i = 0, Banks.COUNT do
     Banks[i] = {}
-    for j = 0, 254 do
+    for j = 0, Banks.SIZE do
         Banks[i][j] = 0
     end
 end
@@ -256,9 +259,11 @@ end
 --
 -- @todo Delete this, and implement bit operations over Banks.
 BitBanks = {}
-for i = 0, 15 do
+BitBanks.COUNT = 15
+BitBanks.SIZE = 255
+for i = 0, BitBanks.COUNT do
     BitBanks[i] = {}
-    for j = 0, 255 do
+    for j = 0, BitBanks.SIZE do
         BitBanks[i][j] = {}
         for k = 0, 8 do
             BitBanks[i][j][k] = 0

+ 2 - 0
data/data/scripts/menu/name_menu.lua

@@ -174,6 +174,8 @@ UiContainer.NameMenu = {
         UiContainer.current_submenu = ""
         self.options_position = 1
         self.confirm_position = 1
+        ui_manager:get_widget("NameMenu.Container.Confirm.Cursor"):set_default_animation("Position" .. self.confirm_position)
+        ui_manager:get_widget("NameMenu.Container.Options.Cursor"):set_default_animation("Position" .. self.options_position)
         ui_manager:get_widget("NameMenu.Container.Character.Portrait"):set_image("images/characters/" .. tostring(self.id) .. ".png")
         if (string.lower(Characters[self.id].name) == "ex-soldier") then
             -- HACK: Hack for Ex-Soldier default. Can this be extracted from menu.lgp?

+ 4 - 4
data/data/scripts/save.lua

@@ -88,8 +88,8 @@ save_game = function(slot, force)
             -- TODO: Character statuses
         end
     end
-    for b = 0, 14 do
-        for a = 0, 254 do
+    for b = 0, Banks.COUNT do
+        for a = 0, Banks.SIZE do
             if (Banks[b] ~= nil and Banks[b][a] ~= nil and Banks[b][a] ~= 0) then
                 savemap_manager:set_data(b, a, tonumber(Banks[b][a]))
             end
@@ -104,8 +104,8 @@ end
 -- @param slot The slot to load (0-15).
 load_game = function(slot)
     -- Populate banks
-    for b = 0, 14 do
-        for a = 0, 254 do
+    for b = 0, Banks.COUNT do
+        for a = 0, Banks.SIZE do
             Banks[b][a] = savemap_manager:get_slot_data(slot, b, a)
         end
     end

+ 72 - 2
data/data/scripts/system.lua

@@ -774,11 +774,33 @@ end
 -- @param bit The bit to get (0-7)
 -- @return The bit value (1-0)
 function get_bank_bit(bank, address, bit)
-    -- TODO: Validation
+    if bank < 0 or bank > Banks.COUNT or address < 0 or address > Banks.SIZE or bit < 0 or bit > 7 then
+        print("ERROR: Tried to check invalid bit " .. tostring(bit) .. " from bank " .. tostring(bank) .. " at address " .. tostring(address) .. ".")
+        return 0
+    end
     local val = Banks[bank][address]
     return get_byte_nth_bit(val, bit)
 end
 
+--- Sets a bit in an address in a data bank
+--
+-- @param bank The bank (0-14)
+-- @param address The bank address (0-254)
+-- @param bit The bit to set (0-7)
+-- @param value The bit value (1-0)
+function set_bank_bit(bank, address, bit, value)
+    if bank < 0 or bank > Banks.COUNT or address < 0 or address > Banks.SIZE or bit < 0 or bit > 7 then
+        print("ERROR: Tried to set invalid bit " .. tostring(bit) .. " from bank " .. tostring(bank) .. " at address " .. tostring(address) .. ".")
+        return
+    end
+    if value ~= 1 and value ~= 0  then
+        print("ERROR: Tried to set bit to invalid value " .. tostring(value) .. " from bank " .. tostring(bank) .. " at address " .. tostring(address) .. ".")
+        return
+    end
+    local val = Banks[bank][address]
+    Banks[bank][address] =  set_byte_nth_bit(val, bit, value)
+end
+
 --- Obtains a bit from an unsigned byte
 --
 -- @param value The value (0-254)
@@ -786,6 +808,15 @@ end
 -- @return The bit value (1-0)
 function get_byte_nth_bit(value, n)
     if n < 0 or n > 7 then
+        print("ERROR: Tried to get invalid bit " .. tostring(bit) .. " from value " .. tostring(value) .. ".")
+        return 0
+    end
+    if value < 0 then
+        print("ERROR: Tried to get bit " .. tostring(bit) .. " from negative value " .. tostring(value) .. ".")
+        return 0
+    end
+    if value > 254 then
+        print("ERROR: Tried to get bit " .. tostring(bit) .. " from value " .. tostring(value) .. " that exceeds byte size.")
         return 0
     end
     -- Convert to binary string
@@ -795,6 +826,45 @@ function get_byte_nth_bit(value, n)
         s = s .. tostring(temp % 2)
         temp = math.floor(temp / 2)
     end
-    print("S:" .. s)
     return tonumber(string.sub(s, n + 1, n + 1))
 end
+
+--- Obtains a bit from an unsigned byte
+--
+-- @param value The value (0-254)
+-- @param bit The bit to get (0-7)
+-- @return The bit value (1-0)
+function set_byte_nth_bit(value, n, bit)
+    if bit ~= 0 and bit ~= 1 then
+        print("ERROR: Tried to set bit " .. tostring(bit) .. " to an invalid value " .. tostring(bit) .. " from value " .. tostring(value) .. ".")
+        return value
+    end
+    if n < 0 or n > 7 then
+        print("ERROR: Tried to set invalid bit " .. tostring(bit) .. " to " .. tostring(bit) .. " from value " .. tostring(value) .. ".")
+        return value
+    end
+    if value < 0 then
+        print("ERROR: Tried to set bit " .. tostring(bit) .. " to " .. tostring(bit) .. "from negative value " .. tostring(value) .. ".")
+        return value
+    end
+    if value > 254 then
+        print("ERROR: Tried to set bit " .. tostring(bit) .. " to " .. tostring(bit) .. "from value " .. tostring(value) .. " that exceeds byte size.")
+        return value
+    end
+    -- Convert to binary string.
+    local s = ""
+    local temp = value
+    for i = 1, 8 do
+        s = s .. tostring(temp % 2)
+        temp = math.floor(temp / 2)
+    end
+    -- Replace the requested bit.
+    s = string.sub(s, 1, n) .. tostring(bit) .. string.sub(s, n + 2)
+    -- Convert back to a number.
+    val = 0
+    for i = 1, 8 do
+        val = val + (tonumber(string.sub(s, i, i )) * 2 ^ (i - 1))
+    end
+    return math.floor(val)
+end
+set_byte_nth_bit(25, 2, 1)