├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ └── decryption-problem.md └── workflows │ └── CI.yml ├── .gitignore ├── CHANGELOG.MD ├── LICENSE.txt ├── README.MD ├── RPGMakerDecrypter.Cli ├── CommandLineOptions.cs ├── Exceptions │ └── InvalidUsageException.cs ├── MVMZHandler.cs ├── Program.cs ├── RGSSADHandler.cs ├── RPGMakerDecrypter.Cli.csproj └── icon_256x256_cli.ico ├── RPGMakerDecrypter.Common ├── ExceptionLogger.cs ├── RPGMakerDecrypter.Common.csproj └── RPGMakerVersion.cs ├── RPGMakerDecrypter.MVMZ ├── Constants.cs ├── DirectoryFilesDecrypter.cs ├── EncryptionKeyFinder.cs ├── Exceptions │ └── EncryptionKeyException.cs ├── FileDecrypter.cs ├── MV │ ├── MVProjectReconstructor.cs │ └── MvDirectoryFilesDecrypter.cs ├── MZ │ ├── MZProjectReconstructor.cs │ └── MzDirectoryFilesDecrypter.cs ├── ProjectReconstructor.cs ├── RPGMakerDecrypter.MVMZ.csproj └── RPGMakerVersionFinder.cs ├── RPGMakerDecrypter.RGSSAD ├── ArchiveFileNameUtils.cs ├── ArchivedFile.cs ├── BinaryUtils.cs ├── Constants.cs ├── Exceptions │ ├── InvalidArchiveException.cs │ └── UnsupportedArchiveException.cs ├── ProjectGenerator.cs ├── RGSSAD.cs ├── RGSSADv1.cs ├── RGSSADv3.cs └── RPGMakerDecrypter.RGSSAD.csproj ├── RPGMakerDecrypter.Tests ├── BinaryUtilsTests.cs ├── EncryptedArchives │ ├── Game.rgss2a │ ├── Game.rgss3a │ └── Game.rgssad ├── EncryptedFiles │ ├── AudioMpeg │ ├── AudioOrbis │ └── Image ├── FileDecrypterTests.cs ├── FileHelpers.cs ├── RGSSADv1Tests.cs ├── RGSSADv3Tests.cs └── RPGMakerDecrypter.Tests.csproj ├── RPGMakerDecrypter.sln └── Resources └── icon_256x256.xcf /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/decryption-problem.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Decryption problem 3 | about: Report a game that you're unable to decrypt 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Link to download the game/archive you're unable to decrypt** 11 | Link to game: 12 | 13 | **Describe the problem** 14 | A clear and concise description of what the problem is. Include possible error message from a RPGMakerDecrypter. 15 | 16 | **Details (please complete the following information):** 17 | - OS: [e.g. Windows/MacOs/Linux] 18 | - RPGMakerDecrypter version: [v.3.x.x] 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | jobs: 9 | build_cli: 10 | 11 | env: 12 | BUILD_CONFIG: 'Release' 13 | CLI_PROJECT: 'RPGMakerDecrypter.Cli/RPGMakerDecrypter.Cli.csproj' 14 | 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Fetch tags 21 | run: git fetch --all --tags 22 | 23 | - name: Setup NuGet 24 | uses: NuGet/setup-nuget@v1.0.5 25 | 26 | - name: Setup .NET 27 | uses: actions/setup-dotnet@v1 28 | with: 29 | dotnet-version: 8.0.x 30 | 31 | - name: Restore dependencies 32 | run: dotnet restore 33 | 34 | - name: Build 35 | run: dotnet build --configuration $Env:BUILD_CONFIG --no-restore 36 | 37 | - name: Publish Linux CLI 38 | run: dotnet publish $Env:CLI_PROJECT --configuration $Env:BUILD_CONFIG -r linux-x64 -p:PublishSingleFile=true --self-contained true --output $Env:GITHUB_WORKSPACE 39 | 40 | - name: Publish Windows CLI 41 | run: dotnet publish $Env:CLI_PROJECT --configuration $Env:BUILD_CONFIG -r win-x64 -p:PublishSingleFile=true --self-contained true --output $Env:GITHUB_WORKSPACE 42 | 43 | - uses: ncipollo/release-action@v1.12.0 44 | with: 45 | bodyFile: CHANGELOG.MD 46 | artifacts: "RPGMakerDecrypter-cli,RPGMakerDecrypter-cli.exe" 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.*~ 3 | project.lock.json 4 | .DS_Store 5 | *.pyc 6 | nupkg/ 7 | 8 | # Visual Studio Code 9 | .vscode 10 | 11 | # Rider 12 | .idea 13 | 14 | # User-specific files 15 | *.suo 16 | *.user 17 | *.userosscache 18 | *.sln.docstates 19 | 20 | # Build results 21 | [Dd]ebug/ 22 | [Dd]ebugPublic/ 23 | [Rr]elease/ 24 | [Rr]eleases/ 25 | x64/ 26 | x86/ 27 | build/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Oo]ut/ 32 | msbuild.log 33 | msbuild.err 34 | msbuild.wrn 35 | 36 | # Visual Studio 2015 37 | .vs/ 38 | 39 | packages/ 40 | launchSettings.json -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | # Changed in this release 2 | 3 | After a major architectural overhaul, I've decided to upgrade the RPG Maker Decrypter version to v3.0.0! 4 | 5 | Biggest change in this release is the new and exiting support for the RPG Maker MV and MZ decryption. With this change, the RPG Maker Decrypter has become the only tool you will ever need to decrypt RPG Maker games made with any version. 6 | 7 | With this change also becomes sad news for the fans of the GUI version. Because I do not have a personal Windows development environment anymore and thus am unable to update the Forms application, I've decided to drop its support. I removed the sources (including the experimental GTK UI), but they remain available in this [commit](https://github.com/uuksu/RPGMakerDecrypter/tree/1a24e8c0a9bbf7b9b1cb030a6a2eb20882e6df15). Maybe someone else would like to create a cross-platform UI (for example with Avalonia) or create a web service utilizing my code (that will be always FOSS with liberal license). 8 | 9 | Application has also a new (not really) fancy icon! -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mikko Uuksulainen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # RPG Maker Decrypter 2 | 3 | RPG Maker Decrypter can be used to extract encrypted archives and files created with **RPG Maker XP, VX VX Ace, MV or MZ**. 4 | It can be also used to recreate a best guess of the original project. 5 | 6 | ## Usage 7 | 8 | RPG Maker Decrypter is an advanced CLI application. To use it, you have a have basic skills of running terminal applications. 9 | 10 | To decrypt RPG Maker XP, VX and VX Ace games, give the encrypted archive file as input (usually files with .rgssad, .rgss2a or .rgss3a extension, but these might also be renamed to something else). To decrypt MV or MZ games, give the game deployment root directory as the input. 11 | 12 | To get available commands, use: 13 | 14 | RPGMakerDecrypter-cli 15 | 16 | To extract archive to same directory where it exists, use 17 | 18 | RPGMakerDecrypter-cli C:\MyRPGMakerGame\Game.rgssad 19 | 20 | To extract RPG Maker XP, VX or VX Ace archive to some other directory, use 21 | 22 | RPGMakerDecrypter-cli C:\MyRPGMakerGame\Game.rgssad --output=C:\OtherDirectory 23 | 24 | To extract RPG Maker MV or MZ game to some other directory, use 25 | 26 | RPGMakerDecrypter-cli C:\MyRPGMakerGame\Game --output=C:\OtherDirectory 27 | 28 | To give a best guess in creating the original project, use 29 | 30 | RPGMakerDecrypter-cli C:\MyRPGMakerGame\Game.rgssad --recreate-project 31 | 32 | You must always specify the output directory when recreating a project for MV or MZ. 33 | 34 | RPGMakerDecrypter-cli C:\MyRPGMakerGame\Game.rgssad --recreate-project --output=C:\OtherDirectory 35 | 36 | ## RPG Maker Decrypter GUI 37 | 38 | Unfortunately, support for GUI has ended. Sources still exist in [history](https://github.com/uuksu/RPGMakerDecrypter/tree/1a24e8c0a9bbf7b9b1cb030a6a2eb20882e6df15). Maybe you would like to continue the development of the Windows-only Forms GUI or create a totally new cross-platform alternative? 39 | 40 | ## Requirements 41 | 42 | * Application works with any platform .NET 8.0 targets. 43 | * .NET Runtime is not required for prebuilt binaries on selected platforms as it is packaged in the executable. 44 | 45 | ## Binaries 46 | 47 | Prebuilt binaries are available for selected platforms. See [Releases](https://github.com/uuksu/RPGMakerDecrypter/releases). 48 | 49 | ## Building 50 | 51 | CLI application will compile with .NET 8.0 SDK, libraries target .NET Standard 2.0. -------------------------------------------------------------------------------- /RPGMakerDecrypter.Cli/CommandLineOptions.cs: -------------------------------------------------------------------------------- 1 | using CommandLine; 2 | 3 | namespace RPGMakerDecrypter.Cli 4 | { 5 | public class CommandLineOptions 6 | { 7 | [Value(0, Required = true, HelpText = "Path to the .rgssad, .rgss2a or .rgss3a file or to MV and MZ deployment directory.")] 8 | public string InputPath { get; set; } 9 | 10 | [Option('o', "output", Required = false, HelpText = "Optional output directory path. Required if --reconstruct-project is set to true and input path is a MV or MZ directory.")] 11 | public string OutputDirectoryPath { get; set; } 12 | 13 | [Option('p', "reconstruct-project", Required = false, HelpText = "If set to true, tries to reconstruct the original project.")] 14 | public bool ReconstructProject { get; set; } 15 | 16 | [Option('w', "overwrite", Required = false, HelpText = "If set to true, destination files are overwritten.")] 17 | public bool Overwrite { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Cli/Exceptions/InvalidUsageException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RPGMakerDecrypter.Cli.Exceptions; 4 | 5 | public class InvalidUsageException(string message) : Exception(message); -------------------------------------------------------------------------------- /RPGMakerDecrypter.Cli/MVMZHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using RPGMakerDecrypter.Cli.Exceptions; 4 | using RPGMakerDecrypter.Common; 5 | using RPGMakerDecrypter.MVMZ; 6 | using RPGMakerDecrypter.MVMZ.Exceptions; 7 | using RPGMakerDecrypter.MVMZ.MV; 8 | using RPGMakerDecrypter.MVMZ.MZ; 9 | using RPGMakerDecrypter.RGSSAD; 10 | 11 | namespace RPGMakerDecrypter.Cli 12 | { 13 | public class MVMZHandler 14 | { 15 | public void Handle(CommandLineOptions commandLineOptions, RPGMakerVersion version) 16 | { 17 | if (commandLineOptions.ReconstructProject && 18 | string.IsNullOrWhiteSpace(commandLineOptions.OutputDirectoryPath)) 19 | { 20 | throw new InvalidUsageException("MV and MZ project reconstruction requires an output path. Please specify a path with -o or --output option."); 21 | } 22 | 23 | if (Directory.Exists(commandLineOptions.OutputDirectoryPath) && !commandLineOptions.Overwrite) 24 | { 25 | throw new InvalidUsageException("Output directory already exists. Please specify a different path or use the -w or --overwrite option."); 26 | } 27 | 28 | try 29 | { 30 | var workingDirectoryPath = commandLineOptions.InputPath; 31 | var deleteEncrypted = false; 32 | 33 | if (commandLineOptions.ReconstructProject) 34 | { 35 | switch (version) 36 | { 37 | case RPGMakerVersion.MV: 38 | new MVProjectReconstructor().Reconstruct( 39 | commandLineOptions.InputPath, 40 | commandLineOptions.OutputDirectoryPath); 41 | break; 42 | case RPGMakerVersion.MZ: 43 | new MZProjectReconstructor().Reconstruct( 44 | commandLineOptions.InputPath, 45 | commandLineOptions.OutputDirectoryPath); 46 | break; 47 | } 48 | 49 | // Change working directory to the project directory where all the necessary files are too 50 | workingDirectoryPath = commandLineOptions.OutputDirectoryPath; 51 | deleteEncrypted = true; 52 | } 53 | 54 | var encryptionKeyFinder = new EncryptionKeyFinder(); 55 | var encryptionKey = encryptionKeyFinder.FindKey(workingDirectoryPath); 56 | 57 | switch (version) 58 | { 59 | case RPGMakerVersion.MV: 60 | new MvDirectoryFilesDecrypter().DecryptFiles( 61 | encryptionKey, 62 | workingDirectoryPath, 63 | deleteEncrypted, 64 | commandLineOptions.Overwrite); 65 | break; 66 | case RPGMakerVersion.MZ: 67 | new MzDirectoryFilesDecrypter().DecryptFiles( 68 | encryptionKey, 69 | workingDirectoryPath, 70 | deleteEncrypted, 71 | commandLineOptions.Overwrite); 72 | break; 73 | } 74 | } 75 | catch (EncryptionKeyException ex) 76 | { 77 | Console.WriteLine(ex.Message); 78 | Environment.Exit(1); 79 | } 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Cli/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CommandLine; 4 | using RPGMakerDecrypter.Cli.Exceptions; 5 | using RPGMakerDecrypter.Common; 6 | using RPGMakerDecrypter.MVMZ; 7 | using RPGMakerDecrypter.RGSSAD; 8 | 9 | namespace RPGMakerDecrypter.Cli 10 | { 11 | static class Program 12 | { 13 | private static CommandLineOptions _commandLineOptions; 14 | 15 | static void Main(string[] args) 16 | { 17 | try 18 | { 19 | var parsedResult = Parser.Default.ParseArguments(args); 20 | _commandLineOptions = parsedResult.Value; 21 | 22 | if (parsedResult.Errors.Any()) 23 | { 24 | Environment.Exit(1); 25 | } 26 | 27 | var version = RGSSAD.RGSSAD.GetRPGMakerVersion(_commandLineOptions.InputPath); 28 | if (version == RPGMakerVersion.Unknown) 29 | { 30 | var mvMzVersionFinder = new RPGMakerVersionFinder(); 31 | version = mvMzVersionFinder.FindVersion(_commandLineOptions.InputPath); 32 | } 33 | 34 | switch (version) 35 | { 36 | case RPGMakerVersion.Xp: 37 | case RPGMakerVersion.Vx: 38 | case RPGMakerVersion.VxAce: 39 | new RGSSADHandler().Handle(_commandLineOptions, version); 40 | break; 41 | case RPGMakerVersion.MV: 42 | case RPGMakerVersion.MZ: 43 | new MVMZHandler().Handle(_commandLineOptions, version); 44 | break; 45 | case RPGMakerVersion.Unknown: 46 | default: 47 | Console.WriteLine("Unable to determinite RPG Maker version. " + 48 | "Please rename RGSSAD file with a extension corresponding to version: " + 49 | "XP: .rgssad, VX: .rgss2a, VX Ace: .rgss3a " + 50 | "or point to MZ or MV directory (."); 51 | Environment.Exit(1); 52 | break; 53 | } 54 | } catch (InvalidUsageException ex) 55 | { 56 | Console.WriteLine(ex.Message); 57 | Environment.Exit(1); 58 | } 59 | catch (Exception ex) 60 | { 61 | var logFilePath = ExceptionLogger.LogException(ex); 62 | Console.WriteLine("Unexpected error happened while trying to extract the archive."); 63 | Console.WriteLine($"Error log has been written to '{logFilePath}'"); 64 | Console.WriteLine("Please create a issue and include the log contents there: https://github.com/uuksu/RPGMakerDecrypter/issues"); 65 | Environment.Exit(1); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Cli/RGSSADHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using RPGMakerDecrypter.Cli.Exceptions; 4 | using RPGMakerDecrypter.Common; 5 | using RPGMakerDecrypter.RGSSAD; 6 | using RPGMakerDecrypter.RGSSAD.Exceptions; 7 | 8 | namespace RPGMakerDecrypter.Cli 9 | { 10 | public class RGSSADHandler 11 | { 12 | public void Handle(CommandLineOptions commandLineOptions, RPGMakerVersion version) 13 | { 14 | string outputDirectoryPath; 15 | 16 | if (commandLineOptions.OutputDirectoryPath != null) 17 | { 18 | if (Directory.Exists(commandLineOptions.OutputDirectoryPath) && !commandLineOptions.Overwrite) 19 | { 20 | throw new InvalidUsageException("Output directory already exists. Please specify a different path or use the -w or --overwrite option."); 21 | } 22 | 23 | if (!Directory.Exists(commandLineOptions.OutputDirectoryPath)) 24 | { 25 | Directory.CreateDirectory(commandLineOptions.OutputDirectoryPath); 26 | } 27 | 28 | outputDirectoryPath = commandLineOptions.OutputDirectoryPath; 29 | } 30 | else 31 | { 32 | var fi = new FileInfo(commandLineOptions.InputPath); 33 | outputDirectoryPath = fi.DirectoryName; 34 | } 35 | 36 | try 37 | { 38 | switch (version) 39 | { 40 | case RPGMakerVersion.Xp: 41 | case RPGMakerVersion.Vx: 42 | var rgssadv1 = new RGSSADv1(commandLineOptions.InputPath); 43 | rgssadv1.ExtractAllFiles(outputDirectoryPath, commandLineOptions.Overwrite); 44 | break; 45 | case RPGMakerVersion.VxAce: 46 | var rgssadv2 = new RGSSADv3(commandLineOptions.InputPath); 47 | rgssadv2.ExtractAllFiles(outputDirectoryPath, commandLineOptions.Overwrite); 48 | break; 49 | } 50 | } 51 | catch (InvalidArchiveException) 52 | { 53 | Console.WriteLine("Archive is invalid or corrupted. Reading failed."); 54 | Console.WriteLine("Please create a issue: https://github.com/uuksu/RPGMakerDecrypter/issues"); 55 | Environment.Exit(1); 56 | } 57 | catch (UnsupportedArchiveException) 58 | { 59 | Console.WriteLine("Archive is not supported or it is corrupted."); 60 | Console.WriteLine("Please create a issue: https://github.com/uuksu/RPGMakerDecrypter/issues"); 61 | Environment.Exit(1); 62 | } 63 | 64 | if (commandLineOptions.ReconstructProject) 65 | { 66 | var outputSameAsArchivePath = new FileInfo(commandLineOptions.InputPath).Directory.FullName == new DirectoryInfo(outputDirectoryPath).FullName; 67 | ProjectGenerator.GenerateProject(version, outputDirectoryPath, !outputSameAsArchivePath); 68 | } 69 | } 70 | } 71 | } 72 | 73 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Cli/RPGMakerDecrypter.Cli.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | Exe 5 | RPGMakerDecrypter-cli 6 | true 7 | true 8 | true 9 | partial 10 | 11 | 12 | icon_256x256_cli.ico 13 | RPG Maker Decrypter 14 | RPG Maker Decrypter 15 | Tool for decrypting RPG Maker XP, VX and VX Ace RGSSAD archives. 16 | Mikko Uuksulainen © 2015-2025 17 | https://github.com/uuksu/RPGMakerDecrypter 18 | 3.0.0 19 | 3.0 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | runtime; build; native; contentfiles; analyzers; buildtransitive 33 | all 34 | 35 | 36 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Cli/icon_256x256_cli.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uuksu/RPGMakerDecrypter/465c88c304cac0b1516a3fbfb0b2a87fb0517736/RPGMakerDecrypter.Cli/icon_256x256_cli.ico -------------------------------------------------------------------------------- /RPGMakerDecrypter.Common/ExceptionLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace RPGMakerDecrypter.Common 5 | { 6 | public static class ExceptionLogger 7 | { 8 | public static string LogException(Exception exception) 9 | { 10 | var outputFilePath = Path.Combine(Path.GetTempPath(), $"RPGMakerDecrypter-{Guid.NewGuid()}.log"); 11 | File.WriteAllText(outputFilePath, exception.ToString()); 12 | 13 | return outputFilePath; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Common/RPGMakerDecrypter.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Common/RPGMakerVersion.cs: -------------------------------------------------------------------------------- 1 | namespace RPGMakerDecrypter.RGSSAD 2 | { 3 | public enum RPGMakerVersion 4 | { 5 | Unknown, 6 | Xp, 7 | Vx, 8 | VxAce, 9 | MV, 10 | MZ 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/Constants.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | 4 | namespace RPGMakerDecrypter.MVMZ 5 | { 6 | public static class Constants 7 | { 8 | public static readonly Dictionary MVFileExtensionMaps = new Dictionary() 9 | { 10 | { ".rpgmvo", ".ogg" }, 11 | { ".rpgmvp", ".png" }, 12 | { ".rpgmvm", ".m4a" } 13 | }; 14 | 15 | public const string MVProjectFileName = "Game.rpgproject"; 16 | public const string MVProjectFileContent = "RPGMV 1.6.3"; 17 | 18 | 19 | public static readonly Dictionary MZFileExtensionMaps = new Dictionary() 20 | { 21 | { ".ogg_", ".ogg" }, 22 | { ".png_", ".png" }, 23 | { ".m4a_", ".m4a" } 24 | }; 25 | 26 | public const string MZProjectFileName = "game.rmmzproject"; 27 | public const string MZProjectFileContent = "RPGMZ 1.8.0"; 28 | 29 | public static readonly string MacOSBundleDirectory = Path.Combine("Contents", "Resources", "app.nw"); 30 | } 31 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/DirectoryFilesDecrypter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | 5 | namespace RPGMakerDecrypter.MVMZ 6 | { 7 | public abstract class DirectoryFilesDecrypter 8 | { 9 | private readonly FileDecrypter _fileDecrypter = new FileDecrypter(); 10 | 11 | /// 12 | /// Decrypts files in a directory based on the provided key and file extension mappings. 13 | /// 14 | /// The encryption key. 15 | /// The path to the directory containing the encrypted files. 16 | /// A dictionary mapping encrypted file extensions to their original extensions. 17 | /// Whether to delete the original encrypted files after decryption. 18 | /// Whether to overwrite existing decrypted files. 19 | protected void Decrypt(byte[] key, string inputPath, Dictionary fileExtensionMaps, 20 | bool deleteEncrypted, bool overwrite) 21 | { 22 | var directory = new DirectoryInfo(inputPath); 23 | 24 | var extensions = fileExtensionMaps.Keys.ToArray(); 25 | 26 | var encryptedFiles = GetEncryptedFiles(directory, extensions); 27 | if (!encryptedFiles.Any()) 28 | { 29 | return; 30 | } 31 | 32 | foreach (var encryptedFile in encryptedFiles) 33 | { 34 | // Encrypted files have a changed extensions, map back to the original extension and create the target path for new file 35 | var fileNameWithoutExtension = new string(encryptedFile.Name.Take(encryptedFile.Name.Length - encryptedFile.Extension.Length).ToArray()); 36 | var realExtension = fileExtensionMaps[encryptedFile.Extension]; 37 | var targetDirectory = encryptedFile.Directory.FullName; 38 | var targetFilePath = Path.Combine(targetDirectory, $"{fileNameWithoutExtension}{realExtension}"); 39 | 40 | if (!overwrite && File.Exists(targetFilePath)) 41 | { 42 | continue; 43 | } 44 | 45 | var decryptedFile = _fileDecrypter.Decrypt(key, encryptedFile.FullName); 46 | 47 | File.WriteAllBytes(targetFilePath, decryptedFile); 48 | 49 | if (deleteEncrypted) 50 | { 51 | File.Delete(encryptedFile.FullName); 52 | } 53 | } 54 | } 55 | 56 | private FileInfo[] GetEncryptedFiles(DirectoryInfo directory, string[] fileExtensions) 57 | { 58 | List encryptedFiles = new List(); 59 | 60 | foreach (var fileExtension in fileExtensions) 61 | { 62 | encryptedFiles.AddRange(directory.GetFiles($"*{fileExtension}", SearchOption.AllDirectories)); 63 | } 64 | 65 | return encryptedFiles.ToArray(); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/EncryptionKeyFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using RPGMakerDecrypter.MVMZ.Exceptions; 6 | 7 | namespace RPGMakerDecrypter.MVMZ 8 | { 9 | public class EncryptionKeyFinder 10 | { 11 | /// 12 | /// Finds the encryption key from the System.json file in the given input path. 13 | /// 14 | /// The path to search for the System.json file. 15 | /// The encryption key as a byte array. 16 | /// Thrown if the System.json file is not found, or if it does not contain a valid encryption key. 17 | public byte[] FindKey(string inputPath) 18 | { 19 | // System.json file contains the encryption key 20 | var systemFile = Directory.GetFiles(inputPath, "System.json", SearchOption.AllDirectories).FirstOrDefault(); 21 | 22 | if (systemFile == null || !File.Exists(systemFile)) 23 | { 24 | throw new EncryptionKeyException("Unable to find the System.json file from the input path."); 25 | } 26 | 27 | var systemFileJson = File.ReadAllText(systemFile); 28 | var systemObject = Newtonsoft.Json.JsonConvert.DeserializeObject>(systemFileJson); 29 | 30 | if (!systemObject.TryGetValue("encryptionKey", out var hashValue)) 31 | { 32 | throw new EncryptionKeyException( 33 | "System.json file does not contain encryption key, unable to decrypt files. " + 34 | "It's also possible that the files are not encrypted."); 35 | } 36 | 37 | // Encryption key is a MD5 hash of the key given when deploying the game. 38 | var md5Hash = (string)hashValue; 39 | 40 | if (string.IsNullOrWhiteSpace(md5Hash) || md5Hash.Length != 32) 41 | { 42 | throw new EncryptionKeyException("Found encryption key but it is too short."); 43 | } 44 | 45 | // MD5 hash to be split into 16 bytes to get the actual encryption key 46 | return MD5HashToByteArray(md5Hash); 47 | 48 | } 49 | 50 | private byte[] MD5HashToByteArray(string md5HashString) 51 | { 52 | var byteArray = new byte[16]; 53 | 54 | for (var i = 0; i < 16; i++) 55 | { 56 | byteArray[i] = Convert.ToByte(md5HashString.Substring(i * 2, 2), 16); 57 | } 58 | 59 | return byteArray; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/Exceptions/EncryptionKeyException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RPGMakerDecrypter.MVMZ.Exceptions 4 | { 5 | public class EncryptionKeyException : Exception 6 | { 7 | public EncryptionKeyException(string message) : base(message) 8 | { 9 | 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/FileDecrypter.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | 4 | namespace RPGMakerDecrypter.MVMZ 5 | { 6 | public class FileDecrypter 7 | { 8 | /// 9 | /// Decrypts a encrypted RPG Maker file using the provided key and input path. 10 | /// 11 | /// The encryption key. 12 | /// The path to the file to be decrypted. 13 | /// The decrypted file data as a byte array. 14 | public byte[] Decrypt(byte[] key, string inputPath) 15 | { 16 | // Skip first 16 bytes from beginning of file (aka. fake header) 17 | var output = File.ReadAllBytes(inputPath).Skip(16).ToArray(); 18 | 19 | // XOR back the first 16 bytes of the file that are encrypted with the key to get the decrypted bytes 20 | for (var i = 0; i < 16; i++) 21 | { 22 | output[i] = (byte) (output[i] ^ key[i]); 23 | } 24 | 25 | return output; 26 | } 27 | 28 | } 29 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/MV/MVProjectReconstructor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | 4 | namespace RPGMakerDecrypter.MVMZ.MV 5 | { 6 | public class MVProjectReconstructor : ProjectReconstructor 7 | { 8 | public override void Reconstruct(string deploymentPath, string outputPath) 9 | { 10 | // Windows, Linux and Web and Android/iOS deployments have a separate www-directory 11 | var dataFilesPath = Path.Combine(deploymentPath, "www"); 12 | 13 | // MacOS deployments have a different structure that needs to be handled separately 14 | var macOSBundleDirectory = Directory.GetDirectories(deploymentPath, "*.app", 15 | SearchOption.TopDirectoryOnly).SingleOrDefault(); 16 | 17 | if (!string.IsNullOrWhiteSpace(macOSBundleDirectory)) 18 | { 19 | dataFilesPath = Path.Combine(macOSBundleDirectory, Constants.MacOSBundleDirectory); 20 | } 21 | 22 | // www-directory essentially contains the project files 23 | base.Reconstruct(dataFilesPath, outputPath); 24 | } 25 | 26 | protected override void CreateProjectFile(string outputPath) 27 | { 28 | File.WriteAllText( 29 | Path.Combine(outputPath, Constants.MVProjectFileName), 30 | Constants.MVProjectFileContent); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/MV/MvDirectoryFilesDecrypter.cs: -------------------------------------------------------------------------------- 1 | namespace RPGMakerDecrypter.MVMZ.MV 2 | { 3 | public class MvDirectoryFilesDecrypter : DirectoryFilesDecrypter 4 | { 5 | public void DecryptFiles(byte[] key, string inputPath, bool deleteEncrypted, bool overwrite) 6 | { 7 | Decrypt(key, inputPath, Constants.MVFileExtensionMaps, deleteEncrypted, overwrite); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/MZ/MZProjectReconstructor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | 4 | namespace RPGMakerDecrypter.MVMZ.MZ 5 | { 6 | public class MZProjectReconstructor : ProjectReconstructor 7 | { 8 | public override void Reconstruct(string deploymentPath, string outputPath) 9 | { 10 | // Windows, Linux and Web and Android/iOS deployments all files are in root of the deployment 11 | var dataFilesPath = deploymentPath; 12 | 13 | // MacOS deployments have a different structure that needs to be handled separately 14 | var macOSBundleDirectory = Directory.GetDirectories(deploymentPath, "*.app", 15 | SearchOption.TopDirectoryOnly).SingleOrDefault(); 16 | 17 | if (!string.IsNullOrWhiteSpace(macOSBundleDirectory)) 18 | { 19 | dataFilesPath = Path.Combine(macOSBundleDirectory, Constants.MacOSBundleDirectory); 20 | } 21 | 22 | base.Reconstruct(dataFilesPath, outputPath); 23 | } 24 | 25 | protected override void CreateProjectFile(string outputPath) 26 | { 27 | File.WriteAllText( 28 | Path.Combine(outputPath, Constants.MZProjectFileName), 29 | Constants.MZProjectFileContent); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/MZ/MzDirectoryFilesDecrypter.cs: -------------------------------------------------------------------------------- 1 | namespace RPGMakerDecrypter.MVMZ.MZ 2 | { 3 | public class MzDirectoryFilesDecrypter : DirectoryFilesDecrypter 4 | { 5 | public void DecryptFiles(byte[] key, string inputPath, bool deleteEncrypted, bool overwrite) 6 | { 7 | Decrypt(key, inputPath, Constants.MZFileExtensionMaps, deleteEncrypted, overwrite); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/ProjectReconstructor.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RPGMakerDecrypter.MVMZ 4 | { 5 | public abstract class ProjectReconstructor 6 | { 7 | // Directories that should exist in the project directory 8 | private readonly string[] _directories = { 9 | "audio", 10 | "css", 11 | "data", 12 | "effects", 13 | "fonts", 14 | "icon", 15 | "img", 16 | "js", 17 | "movies" 18 | }; 19 | 20 | // Files that should exist in the project directory 21 | private readonly string[] _files = 22 | { 23 | "index.html", 24 | "package.json" 25 | }; 26 | 27 | protected abstract void CreateProjectFile(string outputPath); 28 | 29 | public virtual void Reconstruct(string deploymentPath, string outputPath) 30 | { 31 | if (Directory.Exists(outputPath)) 32 | { 33 | Directory.Delete(outputPath, true); 34 | Directory.CreateDirectory(outputPath); 35 | } 36 | 37 | foreach (var directory in _directories) 38 | { 39 | CopyDirectory(Path.Combine(deploymentPath, directory), Path.Combine(outputPath, directory)); 40 | } 41 | 42 | foreach (var file in _files) 43 | { 44 | File.Copy(Path.Combine(deploymentPath, file), Path.Combine(outputPath, file)); 45 | } 46 | 47 | CreateProjectFile(outputPath); 48 | } 49 | 50 | private void CopyDirectory(string sourceDir, string destinationDir) 51 | { 52 | if (!Directory.Exists(sourceDir)) 53 | { 54 | return; 55 | } 56 | 57 | Directory.CreateDirectory(destinationDir); 58 | 59 | foreach (var file in Directory.GetFiles(sourceDir)) 60 | { 61 | var destFilePath = Path.Combine(destinationDir, Path.GetFileName(file)); 62 | File.Copy(file, destFilePath); 63 | } 64 | 65 | foreach (var directory in Directory.GetDirectories(sourceDir)) 66 | { 67 | var destDirectoryPath = Path.Combine(destinationDir, Path.GetFileName(directory)); 68 | CopyDirectory(directory, destDirectoryPath); 69 | } 70 | } 71 | } 72 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/RPGMakerDecrypter.MVMZ.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.MVMZ/RPGMakerVersionFinder.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Linq; 3 | using RPGMakerDecrypter.RGSSAD; 4 | 5 | namespace RPGMakerDecrypter.MVMZ 6 | { 7 | public class RPGMakerVersionFinder 8 | { 9 | /// 10 | /// Attempts to determine the RPG Maker version based on the directory structure of the input path. 11 | /// Can detect only MV or MZ deployments. 12 | /// 13 | /// The path to the RPG Maker deployment directory. 14 | /// The RPG Maker version, or RPGMakerVersion.Unknown if the input path does not exist or its structure is unrecognized. 15 | public RPGMakerVersion FindVersion(string deploymentPath) 16 | { 17 | if (!Directory.Exists(deploymentPath)) 18 | { 19 | return RPGMakerVersion.Unknown; 20 | } 21 | 22 | var directoryInfo = new DirectoryInfo(deploymentPath); 23 | 24 | // MV deployments have a separate www-directory that contains the data 25 | if (directoryInfo.GetDirectories().Any(d => d.Name == "www")) 26 | { 27 | return RPGMakerVersion.MV; 28 | } 29 | 30 | // MacOS deployments look same on both version, but they have a slightly different structure 31 | var macOSBundleDirectory = Directory.GetDirectories(deploymentPath, "*.app", SearchOption.TopDirectoryOnly).SingleOrDefault(); 32 | if (!string.IsNullOrWhiteSpace(macOSBundleDirectory)) 33 | { 34 | // Framework directory is missing from MV, it only exists in MZ 35 | if (Directory.Exists(Path.Combine(macOSBundleDirectory, "Contents", "Frameworks"))) 36 | { 37 | return RPGMakerVersion.MZ; 38 | } 39 | 40 | return RPGMakerVersion.MV; 41 | } 42 | 43 | // MZ deployments have the files in the root directory 44 | return RPGMakerVersion.MZ; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/ArchiveFileNameUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace RPGMakerDecrypter.RGSSAD 7 | { 8 | public static class ArchivedFileNameUtils 9 | { 10 | public static string GetFileName(string name) 11 | { 12 | return GetPathParts(name).Last(); 13 | } 14 | 15 | public static string GetPlatformSpecificPath(string name) 16 | { 17 | var pathParts = GetPathParts(name); 18 | 19 | pathParts = CleanUnicodeCharacters(pathParts); 20 | pathParts = CleanInvalidPathCharacters(pathParts); 21 | 22 | return Path.Combine(pathParts); 23 | } 24 | 25 | private static string[] GetPathParts(string name) 26 | { 27 | // Paths in RGSSAD file names are always with Windows-style delimeters 28 | return name.Split('\\'); 29 | } 30 | 31 | private static string[] CleanUnicodeCharacters(string[] pathParts) 32 | { 33 | var cleanedPathParts = new List(); 34 | var unicodeConstantRegex = new Regex(@"(?i)\\(u|U)([0-9]|[A-F])([0-9]|[A-F])([0-9]|[A-F])([0-9]|[A-F])"); 35 | 36 | foreach (var pathPart in pathParts) 37 | { 38 | cleanedPathParts.Add(unicodeConstantRegex.Replace(pathPart, string.Empty)); 39 | } 40 | 41 | return cleanedPathParts.ToArray(); 42 | } 43 | 44 | private static string[] CleanInvalidPathCharacters(string[] pathParts) 45 | { 46 | var cleanedPathParts = new List(); 47 | 48 | foreach (var pathPart in pathParts) 49 | { 50 | var cleanedPathPart = pathPart; 51 | 52 | foreach(var invalidFileNameChar in Path.GetInvalidFileNameChars()) 53 | { 54 | cleanedPathPart = cleanedPathPart.Replace($"{invalidFileNameChar}", string.Empty); 55 | } 56 | 57 | cleanedPathParts.Add(cleanedPathPart); 58 | } 59 | 60 | return cleanedPathParts.ToArray(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/ArchivedFile.cs: -------------------------------------------------------------------------------- 1 | namespace RPGMakerDecrypter.RGSSAD 2 | { 3 | public class ArchivedFile 4 | { 5 | public string Name { get; set; } 6 | 7 | public int Size { get; set; } 8 | 9 | public long Offset { get; set; } 10 | 11 | public uint Key { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/BinaryUtils.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace RPGMakerDecrypter.RGSSAD 5 | { 6 | public static class BinaryUtils 7 | { 8 | /// 9 | /// Reads C style string from given binary reader. 10 | /// Seeks to end of the string after reading. 11 | /// 12 | /// The binary reader. 13 | /// The maximum length of the string 14 | /// Found string 15 | public static string ReadCString(BinaryReader binaryReader, int maxLength) 16 | { 17 | var beginPosition = binaryReader.BaseStream.Position; 18 | var stringLenght = 0; 19 | 20 | // Searching for end of the C string (byte == 0, NUL character) 21 | do 22 | { 23 | var readByte = binaryReader.ReadByte(); 24 | if (readByte == 0) 25 | break; 26 | 27 | stringLenght += 1; 28 | } while (stringLenght < maxLength); 29 | 30 | // Seeking back to beginning 31 | binaryReader.BaseStream.Seek(beginPosition, SeekOrigin.Begin); 32 | 33 | var result = Encoding.ASCII.GetString(binaryReader.ReadBytes(stringLenght)); 34 | 35 | // Seeking to end position of the string 36 | binaryReader.BaseStream.Seek(beginPosition + stringLenght + 1, SeekOrigin.Begin); 37 | 38 | return result; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/Constants.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace RPGMakerDecrypter.RGSSAD 4 | { 5 | public class Constants 6 | { 7 | public const string RpgMakerXpArchiveName = "Game.rgssad"; 8 | public const string RpgMakerVxArchiveName = "Game.rgss2a"; 9 | public const string RpgMakerVxAceArchiveName = "Game.rgss3a"; 10 | 11 | public static string RpgMakerXpArchiveExtension = RpgMakerXpArchiveName.Split('.').Last(); 12 | public static string RpgMakerVxArchiveExtension = RpgMakerVxArchiveName.Split('.').Last(); 13 | public static string RpgMakerVxAceArchiveExtension = RpgMakerVxAceArchiveName.Split('.').Last(); 14 | 15 | public const string RpgMakerXpProjectFileContent = "RPGXP 1.02"; 16 | public const string RpgMakerVxProjectFileContent = "RPGVX 1.02"; 17 | public const string RpgMakerVxAceProjectFileContent = "RPGVXAce 1.00"; 18 | 19 | public const string RpgMakerXpProjectFileExtension = "rxproj"; 20 | public const string RpgMakerVxProjectFileExtension = "rvproj"; 21 | public const string RpgMakerVxAceProjectFileExtension = "rvproj2"; 22 | 23 | public static readonly string RGSSADHeader = "RGSSAD"; 24 | 25 | public const int RGASSDv1 = 1; 26 | public const int RGASSDv3 = 3; 27 | 28 | public static readonly int[] SupportedRGSSVersions = { RGASSDv1, RGASSDv3 }; 29 | 30 | public static readonly uint RGASSADv1Key = 0xDEADCAFE; 31 | 32 | public const string RPGMakerXpIniFileContents = 33 | "[Game]\r\nLibrary=RGSS104E.dll\r\nScripts=Data\\Scripts.rxdata\r\nTitle=DecryptedProject\r\nRTP1=Standard\r\nRTP2=\r\nRTP3="; 34 | 35 | public const string RPGMakerVxIniFileContents = 36 | "[Game]\r\nRTP=RPGVX\r\nLibrary=RGSS202E.dll\r\nScripts=Data\\Scripts.rvdata\r\nTitle=DecryptedProject"; 37 | 38 | public const string RPGMakerVxAceIniFileContents = 39 | "[Game]\r\nRTP=RPGVXAce\r\nLibrary=System\\RGSS300.dll\r\nScripts=Data\\Scripts.rvdata2\r\nTitle=DecryptedProject"; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/Exceptions/InvalidArchiveException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RPGMakerDecrypter.RGSSAD.Exceptions 4 | { 5 | public class InvalidArchiveException : Exception 6 | { 7 | public InvalidArchiveException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/Exceptions/UnsupportedArchiveException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RPGMakerDecrypter.RGSSAD.Exceptions 4 | { 5 | public class UnsupportedArchiveException : Exception 6 | { 7 | public UnsupportedArchiveException(string message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/ProjectGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace RPGMakerDecrypter.RGSSAD 4 | { 5 | public static class ProjectGenerator 6 | { 7 | /// 8 | /// Generates the project file and ini for given RPG Maker. 9 | /// 10 | /// The version. 11 | /// The output directory path. 12 | /// If set to true, will overwrite existing project file. 13 | public static void GenerateProject(RPGMakerVersion version, string outputDirectoryPath, bool overwrite) 14 | { 15 | string projectFileContent = null; 16 | string projectFileExtension = null; 17 | string iniFileContent = null; 18 | 19 | switch (version) 20 | { 21 | case RPGMakerVersion.Xp: 22 | projectFileContent = Constants.RpgMakerXpProjectFileContent; 23 | projectFileExtension = Constants.RpgMakerXpProjectFileExtension; 24 | iniFileContent = Constants.RPGMakerXpIniFileContents; 25 | break; 26 | case RPGMakerVersion.Vx: 27 | projectFileContent = Constants.RpgMakerVxProjectFileContent; 28 | projectFileExtension = Constants.RpgMakerVxProjectFileExtension; 29 | iniFileContent = Constants.RPGMakerVxIniFileContents; 30 | break; 31 | case RPGMakerVersion.VxAce: 32 | projectFileContent = Constants.RpgMakerVxAceProjectFileContent; 33 | projectFileExtension = Constants.RpgMakerVxAceProjectFileExtension; 34 | iniFileContent = Constants.RPGMakerVxAceIniFileContents; 35 | break; 36 | } 37 | 38 | var projectFilePath = Path.Combine(outputDirectoryPath, $"Game.{projectFileExtension}"); 39 | var iniFilePath = Path.Combine(outputDirectoryPath, "Game.ini"); 40 | 41 | if(overwrite) 42 | { 43 | File.WriteAllText(projectFilePath, projectFileContent); 44 | } 45 | 46 | if(overwrite) 47 | { 48 | File.WriteAllText(iniFilePath, iniFileContent); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/RGSSAD.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using RPGMakerDecrypter.RGSSAD.Exceptions; 6 | 7 | namespace RPGMakerDecrypter.RGSSAD 8 | { 9 | /// 10 | /// Represents RPG Maker RGSS Encrypted Archive. 11 | /// 12 | public class RGSSAD : IDisposable 13 | { 14 | protected readonly string FilePath; 15 | protected readonly BinaryReader BinaryReader; 16 | 17 | public List ArchivedFiles { get; set; } 18 | 19 | protected RGSSAD(string filePath) 20 | { 21 | this.FilePath = filePath; 22 | BinaryReader = new BinaryReader(new FileStream(filePath, FileMode.Open)); 23 | } 24 | 25 | /// 26 | /// Gets the version of RGSSAD. 27 | /// 28 | /// FilePath to RGSSAD archive 29 | /// 30 | /// 31 | /// Archive is in invalid format. 32 | /// or 33 | /// Header was not found for archive. 34 | /// 35 | protected int GetVersion() 36 | { 37 | string header; 38 | 39 | try 40 | { 41 | header = BinaryUtils.ReadCString(BinaryReader, 7); 42 | } 43 | catch (Exception) 44 | { 45 | throw new InvalidArchiveException("Archive is in invalid format."); 46 | } 47 | 48 | if (header != Constants.RGSSADHeader) 49 | { 50 | throw new InvalidArchiveException("Header was not found for archive."); 51 | } 52 | 53 | int result = BinaryReader.ReadByte(); 54 | 55 | if (!Constants.SupportedRGSSVersions.Contains(result)) 56 | { 57 | result = -1; 58 | } 59 | 60 | BinaryReader.BaseStream.Seek(0, SeekOrigin.Begin); 61 | 62 | return result; 63 | } 64 | 65 | /// 66 | /// Extracts all files. 67 | /// 68 | /// Output directory path 69 | /// if set to true, overrides existing files 70 | public void ExtractAllFiles(string outputDirectoryPath, bool overrideExisting = false) 71 | { 72 | foreach (var archivedFile in ArchivedFiles) 73 | { 74 | ExtractFile(archivedFile, outputDirectoryPath, overrideExisting); 75 | } 76 | } 77 | 78 | /// 79 | /// Extracts single file from the file. 80 | /// 81 | /// Archived file 82 | /// Output directory path 83 | /// If set to true, overrides existing files 84 | /// If set to true, creates directory specified in encrypted file name 85 | /// Invalid file path. Archive could be corrupted. 86 | private void ExtractFile(ArchivedFile archivedFile, string outputDirectoryPath, bool overrideExisting = false, bool createDirectory = true) 87 | { 88 | var platformSpecificArchiveFilePath = ArchivedFileNameUtils.GetPlatformSpecificPath(archivedFile.Name); 89 | 90 | string outputPath; 91 | 92 | if (createDirectory) 93 | { 94 | var directoryPath = Path.GetDirectoryName(platformSpecificArchiveFilePath); 95 | 96 | if (directoryPath == null) 97 | { 98 | throw new Exception("Invalid file path. Archive could be corrupted."); 99 | } 100 | 101 | if (!Directory.Exists(Path.Combine(outputDirectoryPath, directoryPath))) 102 | { 103 | Directory.CreateDirectory(Path.Combine(outputDirectoryPath, directoryPath)); 104 | } 105 | 106 | outputPath = Path.Combine(outputDirectoryPath, platformSpecificArchiveFilePath); 107 | } 108 | else 109 | { 110 | var fileName = Path.GetFileName(platformSpecificArchiveFilePath); 111 | outputPath = Path.Combine(outputDirectoryPath, fileName); 112 | } 113 | 114 | // Override existing file flag is set to true 115 | if (File.Exists(outputPath) && !overrideExisting) 116 | { 117 | return; 118 | } 119 | 120 | BinaryReader.BaseStream.Seek(archivedFile.Offset, SeekOrigin.Begin); 121 | var data = BinaryReader.ReadBytes(archivedFile.Size); 122 | 123 | var binaryWriter = new BinaryWriter(File.OpenWrite(outputPath)); 124 | 125 | binaryWriter.Write(DecryptFileData(data, archivedFile.Key)); 126 | 127 | binaryWriter.Close(); 128 | } 129 | 130 | /// 131 | /// Decrypts the file from given bytes using given key. 132 | /// 133 | /// The encrypted file data. 134 | /// The key. 135 | /// 136 | private byte[] DecryptFileData(byte[] encryptedFileData, uint key) 137 | { 138 | var decryptedFileData = new byte[encryptedFileData.Length]; 139 | 140 | var tempKey = key; 141 | var keyBytes = BitConverter.GetBytes(key); 142 | var j = 0; 143 | 144 | for (var i = 0; i <= encryptedFileData.Length - 1; i++) 145 | { 146 | if (j == 4) 147 | { 148 | j = 0; 149 | tempKey *= 7; 150 | tempKey += 3; 151 | keyBytes = BitConverter.GetBytes(tempKey); 152 | } 153 | 154 | decryptedFileData[i] = (byte)(encryptedFileData[i] ^ keyBytes[j]); 155 | 156 | j += 1; 157 | } 158 | 159 | return decryptedFileData; 160 | } 161 | 162 | public void Dispose() 163 | { 164 | BinaryReader.Close(); 165 | BinaryReader.Dispose(); 166 | } 167 | 168 | /// 169 | /// Gets the RPG Maker version based on RGASSD file extension. 170 | /// 171 | /// Path to RGSSAD file 172 | public static RPGMakerVersion GetRPGMakerVersion(string inputPath) 173 | { 174 | if (!File.Exists(inputPath)) 175 | { 176 | return RPGMakerVersion.Unknown; 177 | } 178 | 179 | var fi = new FileInfo(inputPath); 180 | 181 | if(fi.Extension.EndsWith(Constants.RpgMakerXpArchiveExtension)) 182 | { 183 | return RPGMakerVersion.Xp; 184 | } 185 | 186 | if (fi.Extension.EndsWith(Constants.RpgMakerVxArchiveExtension)) 187 | { 188 | return RPGMakerVersion.Vx; 189 | } 190 | 191 | if (fi.Extension.EndsWith(Constants.RpgMakerVxAceArchiveExtension)) 192 | { 193 | return RPGMakerVersion.VxAce; 194 | } 195 | 196 | return RPGMakerVersion.Unknown; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/RGSSADv1.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | using RPGMakerDecrypter.RGSSAD.Exceptions; 5 | 6 | namespace RPGMakerDecrypter.RGSSAD 7 | { 8 | /// 9 | /// Represents RGSSAD format used in RPG Maker XP and VX. 10 | /// 11 | public class RGSSADv1 : RGSSAD 12 | { 13 | public RGSSADv1(string filePath) : base(filePath) 14 | { 15 | var version = GetVersion(); 16 | 17 | if (version != Constants.RGASSDv1) 18 | { 19 | throw new InvalidArchiveException("Archive is in invalid format."); 20 | } 21 | 22 | ReadRGSSAD(); 23 | } 24 | 25 | /// 26 | /// Reads the contents of RGSSAD archive and populates ArchivedFiles property. 27 | /// 28 | private void ReadRGSSAD() 29 | { 30 | var key = Constants.RGASSADv1Key; 31 | 32 | ArchivedFiles = new List(); 33 | 34 | BinaryReader.BaseStream.Seek(8, SeekOrigin.Begin); 35 | while (true) 36 | { 37 | var archivedFile = new ArchivedFile(); 38 | 39 | var length = DecryptInteger(BinaryReader.ReadInt32(), ref key); 40 | archivedFile.Name = DecryptFilename(BinaryReader.ReadBytes(length), ref key); 41 | archivedFile.Size = DecryptInteger(BinaryReader.ReadInt32(), ref key); 42 | archivedFile.Offset = BinaryReader.BaseStream.Position; 43 | archivedFile.Key = key; 44 | ArchivedFiles.Add(archivedFile); 45 | 46 | BinaryReader.BaseStream.Seek(archivedFile.Size, SeekOrigin.Current); 47 | if (BinaryReader.BaseStream.Position == BinaryReader.BaseStream.Length) 48 | break; 49 | } 50 | } 51 | /// 52 | /// Decrypts integer from given value. 53 | /// Proceeds key forward by calculating new value. 54 | /// 55 | /// Encrypted value 56 | /// Key 57 | /// Decrypted integer 58 | private int DecryptInteger(int value, ref uint key) 59 | { 60 | var result = value ^ key; 61 | 62 | key *= 7; 63 | key += 3; 64 | 65 | return (int)result; 66 | } 67 | 68 | /// 69 | /// Decrypts file name from given bytes using given key. 70 | /// Proceeds key forward by calculating new value. 71 | /// 72 | /// Encrypted filename 73 | /// Key 74 | /// Decrypted filename 75 | private string DecryptFilename(byte[] encryptedName, ref uint key) 76 | { 77 | var decryptedName = new byte[encryptedName.Length]; 78 | 79 | for (var i = 0; i <= encryptedName.Length - 1; i++) 80 | { 81 | decryptedName[i] = (byte)(encryptedName[i] ^ (key & 0xff)); 82 | 83 | key *= 7; 84 | key += 3; 85 | } 86 | 87 | var result = Encoding.UTF8.GetString(decryptedName); 88 | 89 | return result; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/RGSSADv3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using RPGMakerDecrypter.RGSSAD.Exceptions; 6 | 7 | namespace RPGMakerDecrypter.RGSSAD 8 | { 9 | /// 10 | /// Represents RGSSAD format used in RPG Maker VX Ace. 11 | /// 12 | public class RGSSADv3 : RGSSAD 13 | { 14 | public RGSSADv3(string filePath) : base(filePath) 15 | { 16 | var version = GetVersion(); 17 | 18 | if (version != Constants.RGASSDv3) 19 | { 20 | throw new InvalidArchiveException("Archive is in invalid format."); 21 | } 22 | 23 | ReadRGSSAD(); 24 | } 25 | 26 | /// 27 | /// Reads the contents of RGSSAD archive and populates ArchivedFiles property. 28 | /// 29 | private void ReadRGSSAD() 30 | { 31 | BinaryReader.BaseStream.Seek(8, SeekOrigin.Begin); 32 | 33 | var key = (uint)BinaryReader.ReadInt32(); 34 | key *= 9; 35 | key += 3; 36 | 37 | ArchivedFiles = new List(); 38 | 39 | while (true) 40 | { 41 | var archivedFile = new ArchivedFile(); 42 | archivedFile.Offset = DecryptInteger(BinaryReader.ReadInt32(), key); 43 | archivedFile.Size = DecryptInteger(BinaryReader.ReadInt32(), key); 44 | archivedFile.Key = (uint)DecryptInteger(BinaryReader.ReadInt32(), key); 45 | 46 | var length = DecryptInteger(BinaryReader.ReadInt32(), key); 47 | 48 | if (archivedFile.Offset == 0) 49 | { 50 | break; 51 | } 52 | 53 | archivedFile.Name = DecryptFilename(BinaryReader.ReadBytes(length), key); 54 | 55 | ArchivedFiles.Add(archivedFile); 56 | } 57 | } 58 | 59 | /// 60 | /// Decrypts integer from given value. 61 | /// 62 | /// Encrypted value 63 | /// Key 64 | /// Decrypted integer 65 | private int DecryptInteger(int value, uint key) 66 | { 67 | var result = value ^ key; 68 | return (int)result; 69 | } 70 | 71 | /// 72 | /// Decrypts file name from given bytes using given key. 73 | /// 74 | /// Encrypted filename 75 | /// Key 76 | /// Decrypted filename 77 | private string DecryptFilename(byte[] encryptedName, uint key) 78 | { 79 | var decryptedName = new byte[encryptedName.Length]; 80 | 81 | var keyBytes = BitConverter.GetBytes(key); 82 | 83 | var j = 0; 84 | for (var i = 0; i <= encryptedName.Length - 1; i++) 85 | { 86 | if (j == 4) 87 | j = 0; 88 | decryptedName[i] = (byte)(encryptedName[i] ^ keyBytes[j]); 89 | j += 1; 90 | } 91 | 92 | var result = Encoding.UTF8.GetString(decryptedName); 93 | 94 | return result; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.RGSSAD/RPGMakerDecrypter.RGSSAD.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | RPGMakerDecrypter.RGSSAD 6 | 7 | 8 | 9 | RPG Maker Decrypter 10 | RPG Maker Decrypter 11 | Tool for decrypting RPG Maker XP, VX and VX Ace RGSSAD archives. 12 | Mikko Uuksulainen © 2015-2023 13 | https://github.com/uuksu/RPGMakerDecrypter 14 | 2.0.0 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/BinaryUtilsTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Security.AccessControl; 4 | using NUnit; 5 | using NUnit.Framework; 6 | using RPGMakerDecrypter.RGSSAD; 7 | 8 | namespace RPGMakerDecrypter.Tests 9 | { 10 | public class BinaryUtilsTests 11 | { 12 | [Test] 13 | public void RPGMakerXpArchiveVersionIsOne() 14 | { 15 | FileHelpers.CopyArchives(); 16 | 17 | using (var binaryReader = new BinaryReader(new FileStream(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerXpArchiveName), FileMode.Open))) 18 | { 19 | var s = BinaryUtils.ReadCString(binaryReader, 7); 20 | var version = binaryReader.ReadByte(); 21 | 22 | Assert.That(version, Is.EqualTo(1)); 23 | } 24 | 25 | FileHelpers.CleanupArchives(); 26 | } 27 | 28 | [Test] 29 | public void RPGMakerVxArchiveVersionIsOne() 30 | { 31 | FileHelpers.CopyArchives(); 32 | 33 | using (var binaryReader = new BinaryReader(new FileStream(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxArchiveName), FileMode.Open))) 34 | { 35 | var s = BinaryUtils.ReadCString(binaryReader, 7); 36 | var version = binaryReader.ReadByte(); 37 | 38 | Assert.That(version, Is.EqualTo(1)); 39 | } 40 | 41 | FileHelpers.CleanupArchives(); 42 | } 43 | 44 | [Test] 45 | public void RPGMakerVxAceArchiveVersionIsThree() 46 | { 47 | FileHelpers.CopyArchives(); 48 | 49 | using (var binaryReader = new BinaryReader(new FileStream(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxAceArchiveName), FileMode.Open))) 50 | { 51 | var s = BinaryUtils.ReadCString(binaryReader, 7); 52 | var version = binaryReader.ReadByte(); 53 | 54 | Assert.That(version, Is.EqualTo(3)); 55 | } 56 | 57 | FileHelpers.CleanupArchives(); 58 | } 59 | 60 | [Test] 61 | public void ReadCStringReturnsCorrectHeaderForArchives() 62 | { 63 | FileHelpers.CopyArchives(); 64 | 65 | using (var binaryReader = new BinaryReader(new FileStream(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerXpArchiveName), FileMode.Open))) 66 | { 67 | var s = BinaryUtils.ReadCString(binaryReader, 7); 68 | Assert.That(s, Is.EqualTo(Constants.RGSSADHeader)); 69 | } 70 | 71 | using (var binaryReader = new BinaryReader(new FileStream(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxArchiveName), FileMode.Open))) 72 | { 73 | var s = BinaryUtils.ReadCString(binaryReader, 7); 74 | Assert.That(s, Is.EqualTo(Constants.RGSSADHeader)); 75 | } 76 | 77 | using (var binaryReader = new BinaryReader(new FileStream(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxAceArchiveName), FileMode.Open))) 78 | { 79 | var s = BinaryUtils.ReadCString(binaryReader, 7); 80 | Assert.That(s, Is.EqualTo(Constants.RGSSADHeader)); 81 | } 82 | 83 | FileHelpers.CleanupArchives(); 84 | } 85 | 86 | 87 | } 88 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/EncryptedArchives/Game.rgss2a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uuksu/RPGMakerDecrypter/465c88c304cac0b1516a3fbfb0b2a87fb0517736/RPGMakerDecrypter.Tests/EncryptedArchives/Game.rgss2a -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/EncryptedArchives/Game.rgss3a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uuksu/RPGMakerDecrypter/465c88c304cac0b1516a3fbfb0b2a87fb0517736/RPGMakerDecrypter.Tests/EncryptedArchives/Game.rgss3a -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/EncryptedArchives/Game.rgssad: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uuksu/RPGMakerDecrypter/465c88c304cac0b1516a3fbfb0b2a87fb0517736/RPGMakerDecrypter.Tests/EncryptedArchives/Game.rgssad -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/EncryptedFiles/AudioMpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uuksu/RPGMakerDecrypter/465c88c304cac0b1516a3fbfb0b2a87fb0517736/RPGMakerDecrypter.Tests/EncryptedFiles/AudioMpeg -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/EncryptedFiles/AudioOrbis: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uuksu/RPGMakerDecrypter/465c88c304cac0b1516a3fbfb0b2a87fb0517736/RPGMakerDecrypter.Tests/EncryptedFiles/AudioOrbis -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/EncryptedFiles/Image: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uuksu/RPGMakerDecrypter/465c88c304cac0b1516a3fbfb0b2a87fb0517736/RPGMakerDecrypter.Tests/EncryptedFiles/Image -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/FileDecrypterTests.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using NUnit.Framework; 3 | using RPGMakerDecrypter.MVMZ; 4 | 5 | namespace RPGMakerDecrypter.Tests; 6 | 7 | public class FileDecrypterTests 8 | { 9 | [Test] 10 | public void FilesDecryptsCorrectly() 11 | { 12 | FileHelpers.CopyEncryptedFiles(); 13 | 14 | // Bytes of "12345" hashed to "827ccb0eea8a706c4c34a16891f84e7b" 15 | byte[] key = 16 | [ 17 | 130, 124, 203, 14, 234, 138, 112, 108, 76, 52, 161, 104, 145, 248, 78, 123 18 | ]; 19 | 20 | var fileDecryptor = new FileDecrypter(); 21 | 22 | var imageBytes = fileDecryptor.Decrypt(key, Path.Combine(FileHelpers.TempDirectoryPath, "Image")); 23 | var audioOrbisBytes = fileDecryptor.Decrypt(key, Path.Combine(FileHelpers.TempDirectoryPath, "AudioOrbis")); 24 | var audioMpegBytes = fileDecryptor.Decrypt(key, Path.Combine(FileHelpers.TempDirectoryPath, "AudioMpeg")); 25 | 26 | var imageSha1 = Convert.ToHexString(SHA1.HashData(imageBytes)).ToLower(); 27 | var audioOrbisSha1 = Convert.ToHexString(SHA1.HashData(audioOrbisBytes)).ToLower(); 28 | var audioMpegSha1 = Convert.ToHexString(SHA1.HashData(audioMpegBytes)).ToLower(); 29 | 30 | Assert.That(imageSha1 == "1d47f411bf4f6df654398faeae8f944f954258bf", Is.True); 31 | Assert.That(audioOrbisSha1 == "90d87198849f7bdfdd4eacd611de3a2540925813", Is.True); 32 | Assert.That(audioMpegSha1 == "f59b4b2f293d1964d85bc1a2e96e345ba22996b4", Is.True); 33 | 34 | FileHelpers.CleanupEncryptedFiles(); 35 | } 36 | } -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/FileHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using RPGMakerDecrypter.RGSSAD; 8 | 9 | namespace RPGMakerDecrypter.Tests 10 | { 11 | static class FileHelpers 12 | { 13 | public static string TempDirectoryPath = "Temp"; 14 | 15 | public static void CopyArchives() 16 | { 17 | if (Directory.Exists(TempDirectoryPath)) 18 | { 19 | Directory.Delete(TempDirectoryPath, true); 20 | } 21 | 22 | Directory.CreateDirectory(TempDirectoryPath); 23 | 24 | File.Copy(Path.Combine("EncryptedArchives", Constants.RpgMakerXpArchiveName), Path.Combine(TempDirectoryPath, Constants.RpgMakerXpArchiveName)); 25 | File.Copy(Path.Combine("EncryptedArchives", Constants.RpgMakerVxArchiveName), Path.Combine(TempDirectoryPath, Constants.RpgMakerVxArchiveName)); 26 | File.Copy(Path.Combine("EncryptedArchives", Constants.RpgMakerVxAceArchiveName), Path.Combine(TempDirectoryPath, Constants.RpgMakerVxAceArchiveName)); 27 | } 28 | 29 | public static void CopyEncryptedFiles() 30 | { 31 | if (Directory.Exists(TempDirectoryPath)) 32 | { 33 | Directory.Delete(TempDirectoryPath, true); 34 | } 35 | 36 | Directory.CreateDirectory(TempDirectoryPath); 37 | 38 | File.Copy(Path.Combine("EncryptedFiles", "Image"), Path.Combine(TempDirectoryPath, "Image")); 39 | File.Copy(Path.Combine("EncryptedFiles", "AudioOrbis"), Path.Combine(TempDirectoryPath, "AudioOrbis")); 40 | File.Copy(Path.Combine("EncryptedFiles", "AudioMpeg"), Path.Combine(TempDirectoryPath, "AudioMpeg")); 41 | } 42 | 43 | public static void CleanupArchives() 44 | { 45 | File.Delete(Path.Combine(TempDirectoryPath, Constants.RpgMakerXpArchiveName)); 46 | File.Delete(Path.Combine(TempDirectoryPath, Constants.RpgMakerVxArchiveName)); 47 | File.Delete(Path.Combine(TempDirectoryPath, Constants.RpgMakerVxAceArchiveName)); 48 | 49 | Directory.Delete(TempDirectoryPath); 50 | } 51 | 52 | public static void CleanupEncryptedFiles() 53 | { 54 | File.Delete(Path.Combine(TempDirectoryPath, "Image")); 55 | File.Delete(Path.Combine(TempDirectoryPath, "AudioOrbis")); 56 | File.Delete(Path.Combine(TempDirectoryPath, "AudioMpeg")); 57 | 58 | Directory.Delete(TempDirectoryPath); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/RGSSADv1Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using RPGMakerDecrypter.RGSSAD; 9 | 10 | namespace RPGMakerDecrypter.Tests 11 | { 12 | public class RGSSADv1Tests 13 | { 14 | [Test] 15 | public void CorrectAmountOfArchivedFilesIsReadFromXpArchive() 16 | { 17 | FileHelpers.CopyArchives(); 18 | 19 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerXpArchiveName)); 20 | 21 | Assert.That(rgssad.ArchivedFiles.Count, Is.EqualTo(16)); 22 | 23 | rgssad.Dispose(); 24 | 25 | FileHelpers.CleanupArchives(); 26 | } 27 | 28 | [Test] 29 | public void CorrectFileNamesAreReadFromXpArchive() 30 | { 31 | FileHelpers.CopyArchives(); 32 | 33 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerXpArchiveName)); 34 | 35 | // Verified with Falos RPG Maker Decrypter 36 | Assert.That(rgssad.ArchivedFiles[0].Name, Is.EqualTo(@"Data\Actors.rxdata")); 37 | Assert.That(rgssad.ArchivedFiles[1].Name, Is.EqualTo(@"Data\Animations.rxdata")); 38 | Assert.That(rgssad.ArchivedFiles[2].Name, Is.EqualTo(@"Data\Armors.rxdata")); 39 | 40 | rgssad.Dispose(); 41 | 42 | FileHelpers.CleanupArchives(); 43 | } 44 | 45 | [Test] 46 | public void CorrectOffsetsAreReadFromXpArchive() 47 | { 48 | FileHelpers.CopyArchives(); 49 | 50 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerXpArchiveName)); 51 | 52 | // Verified with Falos RPG Maker Decrypter 53 | Assert.That(rgssad.ArchivedFiles[0].Offset, Is.EqualTo(34)); 54 | Assert.That(rgssad.ArchivedFiles[1].Offset, Is.EqualTo(11045)); 55 | Assert.That(rgssad.ArchivedFiles[2].Offset, Is.EqualTo(147314)); 56 | 57 | rgssad.Dispose(); 58 | 59 | FileHelpers.CleanupArchives(); 60 | } 61 | 62 | [Test] 63 | public void CorrectSizesAreReadFromXpArchive() 64 | { 65 | FileHelpers.CopyArchives(); 66 | 67 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerXpArchiveName)); 68 | 69 | // Verified with Falos RPG Maker Decrypter 70 | Assert.That(rgssad.ArchivedFiles[0].Size, Is.EqualTo(10981)); 71 | Assert.That(rgssad.ArchivedFiles[1].Size, Is.EqualTo(136243)); 72 | Assert.That(rgssad.ArchivedFiles[2].Size, Is.EqualTo(4285)); 73 | 74 | rgssad.Dispose(); 75 | 76 | FileHelpers.CleanupArchives(); 77 | } 78 | 79 | [Test] 80 | public void CorrectKeysAreReadFromXpArchive() 81 | { 82 | FileHelpers.CopyArchives(); 83 | 84 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerXpArchiveName)); 85 | 86 | // Verified with Falos RPG Maker Decrypter 87 | Assert.That(rgssad.ArchivedFiles[0].Key, Is.EqualTo((uint)0x7B7448AE)); 88 | Assert.That(rgssad.ArchivedFiles[1].Key, Is.EqualTo((uint)0x366D564E)); 89 | Assert.That(rgssad.ArchivedFiles[2].Key, Is.EqualTo((uint)0x222699FE)); 90 | 91 | rgssad.Dispose(); 92 | 93 | FileHelpers.CleanupArchives(); 94 | } 95 | 96 | [Test] 97 | public void CorrectFileNamesAreReadFromVxArchive() 98 | { 99 | FileHelpers.CopyArchives(); 100 | 101 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxArchiveName)); 102 | 103 | // Verified with Falos RPG Maker Decrypter 104 | Assert.That(rgssad.ArchivedFiles[0].Name, Is.EqualTo(@"Data\Actors.rvdata")); 105 | Assert.That(rgssad.ArchivedFiles[1].Name, Is.EqualTo(@"Data\Animations.rvdata")); 106 | Assert.That(rgssad.ArchivedFiles[2].Name, Is.EqualTo(@"Data\Areas.rvdata")); 107 | 108 | rgssad.Dispose(); 109 | 110 | FileHelpers.CleanupArchives(); 111 | } 112 | 113 | [Test] 114 | public void CorrectOffsetsAreReadFromVxArchive() 115 | { 116 | FileHelpers.CopyArchives(); 117 | 118 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxArchiveName)); 119 | 120 | // Verified with Falos RPG Maker Decrypter 121 | Assert.That(rgssad.ArchivedFiles[0].Offset, Is.EqualTo(34)); 122 | Assert.That(rgssad.ArchivedFiles[1].Offset, Is.EqualTo(10951)); 123 | Assert.That(rgssad.ArchivedFiles[2].Offset, Is.EqualTo(139280)); 124 | 125 | rgssad.Dispose(); 126 | 127 | FileHelpers.CleanupArchives(); 128 | } 129 | 130 | [Test] 131 | public void CorrectSizesAreReadFromVxArchive() 132 | { 133 | FileHelpers.CopyArchives(); 134 | 135 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxArchiveName)); 136 | 137 | // Verified with Falos RPG Maker Decrypter 138 | Assert.That(rgssad.ArchivedFiles[0].Size, Is.EqualTo(10887)); 139 | Assert.That(rgssad.ArchivedFiles[1].Size, Is.EqualTo(128304)); 140 | Assert.That(rgssad.ArchivedFiles[2].Size, Is.EqualTo(4)); 141 | 142 | rgssad.Dispose(); 143 | 144 | FileHelpers.CleanupArchives(); 145 | } 146 | 147 | [Test] 148 | public void CorrectKeysAreReadFromVxArchive() 149 | { 150 | FileHelpers.CopyArchives(); 151 | 152 | var rgssad = new RGSSADv1(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxArchiveName)); 153 | 154 | // Verified with Falos RPG Maker Decrypter 155 | Assert.That(rgssad.ArchivedFiles[0].Key, Is.EqualTo((uint)0x7B7448AE)); 156 | Assert.That(rgssad.ArchivedFiles[1].Key, Is.EqualTo((uint)0x366D564E)); 157 | Assert.That(rgssad.ArchivedFiles[2].Key, Is.EqualTo((uint)0x04E0F16D)); 158 | 159 | rgssad.Dispose(); 160 | 161 | FileHelpers.CleanupArchives(); 162 | } 163 | 164 | 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/RGSSADv3Tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using NUnit.Framework; 8 | using RPGMakerDecrypter.RGSSAD; 9 | 10 | namespace RPGMakerDecrypter.Tests 11 | { 12 | public class RGSSADv3Tests 13 | { 14 | [Test] 15 | public void CorrectAmountOfArchivedFilesIsReadFromVxAceArchive() 16 | { 17 | FileHelpers.CopyArchives(); 18 | 19 | var rgssad = new RGSSADv3(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxAceArchiveName)); 20 | 21 | Assert.That(rgssad.ArchivedFiles.Count, Is.EqualTo(16)); 22 | 23 | rgssad.Dispose(); 24 | 25 | FileHelpers.CleanupArchives(); 26 | } 27 | 28 | [Test] 29 | public void CorrectFileNamesAreReadFromVxAceArchive() 30 | { 31 | FileHelpers.CopyArchives(); 32 | 33 | var rgssad = new RGSSADv3(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxAceArchiveName)); 34 | 35 | // Verified with Falos RPG Maker Decrypter 36 | Assert.That(rgssad.ArchivedFiles[0].Name, Is.EqualTo(@"Data\Actors.rvdata2")); 37 | Assert.That(rgssad.ArchivedFiles[1].Name, Is.EqualTo(@"Data\Animations.rvdata2")); 38 | Assert.That(rgssad.ArchivedFiles[2].Name, Is.EqualTo(@"Data\Armors.rvdata2")); 39 | 40 | rgssad.Dispose(); 41 | 42 | FileHelpers.CleanupArchives(); 43 | } 44 | 45 | [Test] 46 | public void CorrectOffsetsAreReadFromVxAceArchive() 47 | { 48 | FileHelpers.CopyArchives(); 49 | 50 | var rgssad = new RGSSADv3(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxAceArchiveName)); 51 | 52 | // Verified with Falos RPG Maker Decrypter 53 | Assert.That(rgssad.ArchivedFiles[0].Offset, Is.EqualTo(605)); 54 | Assert.That(rgssad.ArchivedFiles[1].Offset, Is.EqualTo(3637)); 55 | Assert.That(rgssad.ArchivedFiles[2].Offset, Is.EqualTo(222096)); 56 | 57 | rgssad.Dispose(); 58 | 59 | FileHelpers.CleanupArchives(); 60 | } 61 | 62 | [Test] 63 | public void CorrectSizesAreReadFromVxAceArchive() 64 | { 65 | FileHelpers.CopyArchives(); 66 | 67 | var rgssad = new RGSSADv3(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxAceArchiveName)); 68 | 69 | // Verified with Falos RPG Maker Decrypter 70 | Assert.That(rgssad.ArchivedFiles[0].Size, Is.EqualTo(3032)); 71 | Assert.That(rgssad.ArchivedFiles[1].Size, Is.EqualTo(218459)); 72 | Assert.That(rgssad.ArchivedFiles[2].Size, Is.EqualTo(11472)); 73 | 74 | rgssad.Dispose(); 75 | 76 | FileHelpers.CleanupArchives(); 77 | } 78 | 79 | [Test] 80 | public void CorrectKeysAreReadFromVxAceArchive() 81 | { 82 | FileHelpers.CopyArchives(); 83 | 84 | var rgssad = new RGSSADv3(Path.Combine(FileHelpers.TempDirectoryPath, Constants.RpgMakerVxAceArchiveName)); 85 | 86 | // Verified with Falos RPG Maker Decrypter 87 | Assert.That(rgssad.ArchivedFiles[0].Key, Is.EqualTo((uint)0x00000029)); 88 | Assert.That(rgssad.ArchivedFiles[1].Key, Is.EqualTo((uint)0x00004823)); 89 | Assert.That(rgssad.ArchivedFiles[2].Key, Is.EqualTo((uint)0x000018BE)); 90 | 91 | rgssad.Dispose(); 92 | 93 | FileHelpers.CleanupArchives(); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.Tests/RPGMakerDecrypter.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | PreserveNewest 31 | 32 | 33 | PreserveNewest 34 | 35 | 36 | PreserveNewest 37 | 38 | 39 | PreserveNewest 40 | 41 | 42 | PreserveNewest 43 | 44 | 45 | PreserveNewest 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /RPGMakerDecrypter.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.3.32901.215 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPGMakerDecrypter.RGSSAD", "RPGMakerDecrypter.RGSSAD\RPGMakerDecrypter.RGSSAD.csproj", "{BAEEE442-1888-4340-94BE-FE743C696AE1}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPGMakerDecrypter.Tests", "RPGMakerDecrypter.Tests\RPGMakerDecrypter.Tests.csproj", "{CDFB3E01-DE4D-4FC2-8369-21D9F70595C2}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RPGMakerDecrypter.Cli", "RPGMakerDecrypter.Cli\RPGMakerDecrypter.Cli.csproj", "{D03BC34B-EEB1-4A26-B4FB-190CE153E06A}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Resources", "Resources", "{48D60675-A06B-4683-99A2-F44188D93693}" 13 | ProjectSection(SolutionItems) = preProject 14 | Resources\icon_256x256.pdn = Resources\icon_256x256.pdn 15 | EndProjectSection 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPGMakerDecrypter.Common", "RPGMakerDecrypter.Common\RPGMakerDecrypter.Common.csproj", "{41DB3BF6-A576-4D78-8E77-89A906C9A49A}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RPGMakerDecrypter.MVMZ", "RPGMakerDecrypter.MVMZ\RPGMakerDecrypter.MVMZ.csproj", "{9AE7DB4F-7F8F-4A25-B680-6BE6AF385286}" 20 | EndProject 21 | Global 22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 23 | Debug|Any CPU = Debug|Any CPU 24 | Release|Any CPU = Release|Any CPU 25 | EndGlobalSection 26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 27 | {BAEEE442-1888-4340-94BE-FE743C696AE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {BAEEE442-1888-4340-94BE-FE743C696AE1}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {BAEEE442-1888-4340-94BE-FE743C696AE1}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {BAEEE442-1888-4340-94BE-FE743C696AE1}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {CDFB3E01-DE4D-4FC2-8369-21D9F70595C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {CDFB3E01-DE4D-4FC2-8369-21D9F70595C2}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {CDFB3E01-DE4D-4FC2-8369-21D9F70595C2}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {CDFB3E01-DE4D-4FC2-8369-21D9F70595C2}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {D03BC34B-EEB1-4A26-B4FB-190CE153E06A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {D03BC34B-EEB1-4A26-B4FB-190CE153E06A}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {D03BC34B-EEB1-4A26-B4FB-190CE153E06A}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {D03BC34B-EEB1-4A26-B4FB-190CE153E06A}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {41DB3BF6-A576-4D78-8E77-89A906C9A49A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {41DB3BF6-A576-4D78-8E77-89A906C9A49A}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {41DB3BF6-A576-4D78-8E77-89A906C9A49A}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {41DB3BF6-A576-4D78-8E77-89A906C9A49A}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {9AE7DB4F-7F8F-4A25-B680-6BE6AF385286}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {9AE7DB4F-7F8F-4A25-B680-6BE6AF385286}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {9AE7DB4F-7F8F-4A25-B680-6BE6AF385286}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {9AE7DB4F-7F8F-4A25-B680-6BE6AF385286}.Release|Any CPU.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | GlobalSection(ExtensibilityGlobals) = postSolution 52 | SolutionGuid = {2B50EE10-517D-4BAE-A617-97CAD2922589} 53 | EndGlobalSection 54 | EndGlobal 55 | -------------------------------------------------------------------------------- /Resources/icon_256x256.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uuksu/RPGMakerDecrypter/465c88c304cac0b1516a3fbfb0b2a87fb0517736/Resources/icon_256x256.xcf --------------------------------------------------------------------------------